board에 image 추가하기React+REST API 게시판 구현/BE - TypeORM2021. 9. 24. 12:28
Table of Contents
이미지 업로드 하는 방법
- 이미지를 업로드하는 방법은 두가지다.
- 첫번째는 file upload 버튼을 사용해서 올리게 되는 multi-part 프로토콜 방식이다.
- 이 경우는 브라우저가 해당 프로토콜에 맞게 올려주게 된다. 파일을 업로드하는 메서드는 POST이다.
- 두번째는 JSON 방식으로 보내는 방식이다. 이미지는 binary이므로 JSON으로 보내기 위해서는 string으로 변환해야 한다.
이미지 업로드 및 보기 API 설계
- 이미지를 업로드하는 방법중 첫번째 방법인 file upload 버튼을 사용해서 올리게 되는 multi-part 방법으로 API를 구현해보자.
- 페이지를 리턴하는 페이징 방식에서는 업로드시 한꺼번에 올리고 처리했다면 SPA 방식에서는 SRP 원칙으로 작게 쪼개는게 확장성에 유리하기 때문에 이미지를 업로드하는 별도의 테이블을 설계하고 REST API도 별도로 구축하는것이 확장성이 있다.
- 서버에서 binary 이미지 데이터를 받은 후에 이것을 물리적인 화일로 처리할 지 아니면 DB에 넣어서 처리할 지가 설계를 할 때 고민해야하는 부분이다. file로 처리하는 경우는 multi server일 경우에 처리가 곤란하므로 AWS S3를 사용하는게 좋은 방법이다. 더 좋은 방법은 DB로 처리하는 방법이다.
- 여기에서는 DB에 저장하고 꺼내오는 방법을 살펴보자!
- /api/board – board를 추가할 때, form data를 이용해 multi-part 방식으로 board에 이미지 파일과 title, content를 업로드한다
- /api/image/view/:board_id – board_id에 해당하는 이미지 보기 API. xxx.png 를 보기했을때랑 동일하게 동작되도록 한다.
- form data로 이미지 파일과 나머지 title, content를 받아오기 때문에 기존의 board 추가, 수정 API를 수정하면 된다!
이미지 Entity 설계 / 컨트롤 모듈화 / 라우팅 모듈화
이미지 Entity 설계
- src 아래 entity 폴더에 image.ts 파일을 생성하자.
- data 필드는 type을 longblob으로 설정한다. 이미지 binary 데이터를 저장할 것이므로 blob 타입이여야 하는데, blob은 용량이 작으므로 longblob 타입으로 저장하자.
- 또한 board와 one to one 관계를 맺어주고 JoinColumn을 image쪽에서 만들어서 board의 id를 가지고 이미지를 볼 수 있도록 하자!
src/entity/Image.ts
import {Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn} from "typeorm";
import {Board} from "./Board";
@Entity()
export class Image {
@PrimaryGeneratedColumn()
id: number;
@Column({length: 100, nullable: true})
mimetype: string;
@Column({type: "longblob"})
data: string;
@Column({length: 100, nullable: true})
original_name: string;
@CreateDateColumn()
created: Date;
@OneToOne(type => Board, {onDelete:"CASCADE", onUpdate:"CASCADE"})
@JoinColumn()
board: Board;
}
컨트롤 모듈화
- src 아래 controller 폴더에 ImageController.ts 파일을 생성하고 프로토타입을 구현하자.
src/controller/ImageController.ts
import {Image} from "../entity/Image";
import {getConnection} from "typeorm";
export class ImageController {
}
라우팅 모듈화
- /api/image/[~] 경로를 가진 API를 만들기 전에 /api/image를 처리해줄 라우팅 부분을 모듈화하자.
- src 아래 router 폴더에 image.ts 파일을 만들자.
- 우선 /image를 처리해줄 라우팅을 모듈화 하자.
src/router/image.ts
import {Router} from "express";
import {ImageController} from "../controller/ImageController";
const routes = Router();
export default routes;
- 이제 /api를 처리해주는 라우팅 모듈인 src/router/index.ts 파일에 서브라우팅을 추가하자.
src/router/index.ts
import {Router} from "express";
...
// src/router/inmage.ts 라우팅 모듈
import image from "./image"
const routes = Router();
...
routes.use('/image', image);
export default routes;
기존 board 추가 API 수정
- HTTP 메서드: POST
- url: /api/board
기존 board 추가 컨트롤러 모듈 수정
- 기존의 board 추가 API는 json body에 title, content을 받아왔지만 request body에 form-data로 아래와 같은 형식으로 file, title, image를 받아오게 수정한다.
src/controller/BoardController.ts
- board와 image가 one-to-one 관계이고 image 쪽에서 JoinColumn을 생성하므로 board를 먼저 저장한 후 저장한 board를 image의 board에 추가한다!
- 이미지 파일을 form-data의 file 키로 가져오고 buffer와 originalname, mimetype 세 파트로 구분되어 있으므로 각각 image Entitiy의 속성에 추가한후 먼저 저장한 board의 정보를 image의 board에 추가
// board entity
import {Board} from "../entity/Board";
// image Entity
import {Image} from "../entity/Image";
import {getConnection} from "typeorm";
export class BoardController {
...
// board 추가 컨트롤러
static addBoard = async (req, res) => {
// board 저장
const {title, content} = req.body;
const board = new Board();
board.title = title;
board.content = content;
const result = await getConnection().getRepository(Board).save(board)
// image 저장하면서 one-to-one 관계인 board 추가
let image: Image = new Image();
image.data = req.file.buffer;
image.original_name = req.file.originalname;
image.mimetype = req.file.mimetype;
// image에 board 정보 추가
image.board = result;
await getConnection().getRepository(Image).save(image);
res.send({"message": "success!"})
}
...
}
기존 board 추가 라우팅 모듈 수정
- 우선 Node에서 form-data와 파일 업로드를 처리하기 위해서 multer라는 라이브러리가 필요하다.
- body에서 올라오는 multip-part 이미지 binary 데이터를 모아서 req에 file이라는 속성으로 넘겨준다. 또한 바이너리를 파일로 처리할지 아니면 메모리로 처리할지 결정하는 옵션도 제공해준다.
npm i -S multer
src/router/board.ts
- storage: multer.memoryStorage() – 바이너리를 메모리로 처리하겠다는 옵션이다. 물리적인 파일로 저장하는 것이 아니라 DB에 저장할 것이기 때문에 파일로 저장할 필요가 없으므로 메모리로 처리한다.
- single(‘file’) – single은 바이너리 화일을 하나만 처리하겠다는 옵션이다. file은 클라이언트에서 file upload key에 해당한다. 여기서도 SRP 원칙에 따른것이다. 이미지를 여러장 올리는 API를 한번 사용하나 이미지를 한 장 올리는 API를 여러번 호출하는 것이나 동일하지만 이미지 한 장을 여러번 올리는 API 가 더 확장성이 있다.
- form-data와 파일 업로드를 처리하기 위해 기존 board 추가 라우팅에 multer 설정을 추가하자!
import {Router} from "express";
import {BoardController} from "../controller/BoardController";
// multer 설정
const multer = require('multer');
const upload = multer({storage: multer.memoryStorage()})
const routes = Router();
// form-data를 처리하기 위해 기존 board를 추가하는 라우트에 multer 설정 추가
routes.post('', upload.single('file'), BoardController.addBoard);
- postman에서 POST 메서드로 http://localhost:8080/api/board 주소로 body 부분에서 form-data를 선택하고 key 부분에 file 을 선택하고 나머지 title, comment 값을 추가하자
- 정상적으로 업로드되면 board가 하나 추가되고 image가 하나 추가되는데 image는 board_id라는 board의 id를 가지고 있다!
- 이를 가지고 board의 id에 해당하는 이미지를 보거나 수정할 수 있다!
기존 board 수정 API 수정
- HTTP 메서드: put
- url: /api/board
기존 board 추가 컨트롤러 모듈 수정
- 기존의 board 추가 API는 json body에 수정할 board의 id, title, content 값을 json 형식으로 body로 받아왔지만 request body에 form-data로 아래와 같은 형식으로 board의 id, file, title, image를 받아오게 수정한다.
src/controller/BoardController.ts
- 우선 board의 id를 받아서 해당 board의 title과 content를 수정하고 클라이언트가 이미지 파일을 보낼때만 image의 board_id가 board의 id인 image를 수정하자
- 쉽게 말하면 사진을 선택하지 않으면 title과 comment만 수정하겠다는 의미이다.
// board entity
import {Board} from "../entity/Board";
// image Entity
import {Image} from "../entity/Image";
import {getConnection} from "typeorm";
export class BoardController {
...
// board 수정 컨트롤러
static modifyBoard = async (req, res) => {
// 수정할 board의 id와 title, content
const {id, title, content} = req.body;
const updateOption = {
"title": title,
"content": content
};
// board 먼저 수정
const result = await getConnection().createQueryBuilder().update(Board)
.set(updateOption)
.where("id = :id", {id})
.execute();
// formData에 파일이 있으면 파일 수정!
if (req.file) {
const imageUpdateOption = {
data: req.file.buffer,
original_name: req.file.originalname,
mimetype: req.file.mimetype
}
const result = await getConnection().createQueryBuilder().update(Image)
.set(imageUpdateOption)
.where("board_id = :id", {id})
.execute();
}
res.send({"message": "success!"});
}
...
}
기존 board 수정 라우팅 모듈 수정
src/router/board.ts
- form-data와 파일 업로드를 처리하기 위해 기존 board 수정 라우팅에 multer 설정을 추가하자!
import {Router} from "express";
import {BoardController} from "../controller/BoardController";
// multer 설정
const multer = require('multer');
const upload = multer({storage: multer.memoryStorage()})
const routes = Router();
// form-data를 처리하기 위해 기존 board를 수정하는 라우트에 multer 설정 추가
routes.put('', upload.single('file'), BoardController.modifyBoard);
- postman에서 PUT 메서드로 http://localhost:8080/api/board 주소로 body 부분에서 form-data를 선택하고 key 부분에 file 을 선택하고 수정할 board의 id와 나머지 title, comment 값을 추가하자
- 정상적으로 수정되면 board의 title과 content가 변경되고 이미지 파일을 선택했다면 image의 내용이 변경된다!
- board가 form-data에 넣어준 값으로 변경!
- board가 위에서 아래로 변경!
- image가 form-data에 넣어준 값으로 변경
- image가 위에서 아래로 변경!
+++ file을 선택 안할 경우
- 만약 아래처럼 file을 선택하지 않고 title과 comment만 변경시키면 board만 변경되고 image는 그대로다!
- board는 변경!
- image는 그대로!
board의 이미지 보기 API
- HTTP 메서드: GET
- url: /api/image/view/:board_id
- 클라이언트가 board의 id를 가지고 있으면 url로 바로 사진 파일을 사용할 수 있다
- 브라우저에서 http://localhost8080/api/image/view/:board_id를 하게 되면 브라우저에 이미지가 보인다. 혹은 html 태그에서 <img src = "/api/image/view/:board_id"/> 하게 되면 이미지가 보이게 된다.
- png 파일을 브라우저에서 보면 response 헤더에 Content-type에 image/png, Content-Length: 에 바이너리 데이터가 길이가 들어가고 body에 바이너리 데이터가 들어간다.
- 그래서 response 헤더와 두개의 헤더를 추가했고 body에 바이너리 데이터를 넣었다.
컨트롤 모듈화
src/controller/ImageController.ts
import {Image} from "../entity/Image";
import {getConnection} from "typeorm";
export class ImageController {
static viewImage = async (req, res) => {
const {board_id} = req.params;
const db = getConnection()
.getRepository(Image)
.createQueryBuilder('image')
.where('board_id = :board_id', {board_id})
const image = await db.getOne();
res.writeHead(200, {
'Content-Type': image.mimetype,
'Content-Length': image.data.length
});
res.end(image.data);
}
}
라우팅 모듈화
src/router/image.ts
import {Router} from "express";
import {ImageController} from "../controller/ImageController";
const routes = Router();
routes.get('/view/:board_id', ImageController.viewImage);
export default routes;
- 위에서 추가한 board의 id를 이미지 보기 API의 URI 파라미터로 넘기면 board의 image를 확인할 수 있다.
'React+REST API 게시판 구현 > BE - TypeORM' 카테고리의 다른 글
인증과 권한 설계 (0) | 2021.09.24 |
---|---|
commet CRUD API (0) | 2021.09.24 |
board 추가/보기(페이징)/수정/삭제 API (0) | 2021.09.20 |
컨트롤러, 라우팅 모듈화 (0) | 2021.09.19 |
IDEA에서 DataGrip 연결하기 + entity 설계 (1) | 2021.09.17 |
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!