React+REST API 게시판 구현/BE - TypeORM

board 추가/보기(페이징)/수정/삭제 API

덕구공 2021. 9. 20. 18:51

참고

 

angular, react, vue 등의 최신프런트엔드로 풀스택 개발

백엔드는 spring boot, Node로 프런트엔드는 react, angular, vue 의 최신프런트엔드를 사용하여 풀스택사이트 개발!

eastflag.co.kr


What to do?

 

API 설계

참고 https://eastflag.co.kr/fullstack/rest-with-nodejs/node-rest_board_post/ angular, react, vue 등의 최신프런트엔드로 풀스택 개발 백엔드는 spring boot, Node로 프런트엔드는 react, angular, vue 의 최..

duckgugong.tistory.com

  • 우선 위 링크에 들어가서 기본적인 API 설계를 하자.

board 관련 API 설계

컨트롤 모듈화

  • board와 관련된 API의 입력과 출력을 담당해줄 BoardController.ts 파일을 src 아래 controller 폴더에 만들어주자.

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {
	
}

라우팅 모듈화

  • board 와 관련된 API는 /api/board/[~]의 경로를 가지고 있다.
  • /api/board 뒤의 서브 라우팅을 처리해 줄 board.ts 파일을 src 아래 router 폴더에 만들어주자.

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";

const routes = Router();

export default routes;
  • 그리고 /board를 처리해 줄 부분을 src 아래 router 폴더에 있는 index.ts 파일에 추가하자.

src/router/index.ts

import {Router} from "express";
// src/router/board.ts 라우팅 모듈
import board from "./board";

const routes = Router();

routes.use('/board', board)

export default routes;

board 추가 API 구현

  • HTTP 메서드: POST
  • url: /api/board

controller 모듈

src/controller/BoardController.ts

  • board 인스턴스를 만들고 save를 하면 ORM이 insert 구문을 실행하여 DB에 반영한다!
// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {
    static addBoard = async (req, res) => {
        const {title, content} = req.body;

        const board = new Board();
        board.title = title;
        board.content = content;
        const result = await getConnection().getRepository(Board).save(board);

        res.send(result);
    }
}

 

라우팅 모듈

src/router/board.ts

import {Router} from "express";
// controller 모듈
import {BoardController} from "../controller/BoardController";

const routes = Router();

routes.post('', BoardController.addBoard);

export default routes;


board 전체 보기

  • HTTP 메서드: GET
  • url: api/board/list

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController { 
    
    ...
    
    static findAllBoard = async (req, res) => {
        const boards = await getConnection().getRepository(Board).find();
        res.send(boards);
    }
}

라우팅 모듈

src/router/index.ts

import {Router} from "express";
// controller 모듈
import {BoardController} from "../controller/BoardController";

const routes = Router();

...

routes.get('/list', BoardController.findAllBoard);

export default routes;


board 하나 보기

  • HTTP 메서드: GET
  • url: /api/board/:id

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {
  
	...
    
    static findOneBoard = async (req, res) => {
        const {id} = req.params;

        const board = await getConnection().getRepository(Board).findOne({id});
        res.send(board);
    }
}

라우팅 모듈

src/router/board.ts

import {Router} from "express";
// controller 모듈
import {BoardController} from "../controller/BoardController";

const routes = Router();

...

routes.get('/board/:id', BoardController.findOneBoard);

export default routes;


board 보기 (페이지네이션)

  • 목록이 많아지면 페이지네이션을 구현해야 하는데, 클라이언트에서 페이지네이션을 구성하기 위한 세가지가 있다.
    • 전체 목록 갯수 (서버로부터 리턴 받아야 함)
    • 페이지 사이즈
    • 현재 페이지 번호
  • 예를 들어 3번째 페이지에서 10개를 가져온다고 하자.
    • 페이지 사이즈: 2
    • 현재 페이지 번호: 1
  • MariaDB에서는 LIMIT offset, size와 같은 쿼리문이 있어서 이를 계산할 수 있다.
    • offset: (현재 페이지 번호 - 1) * 페이지 사이즈 => 2 (offset은 0부터)
    • size: 페이지 사이즈 = 10
  • Contoller 에 page_number, page_size 두개의 파라미터를 받고 둘다 null이 아니면 skip과 take를 계산해서 페이징을 수행하자! 만약 둘 중 하나라도 null이면 전체 보기를 리턴하자!
  • 위에 있는 board 전체보기(findAllBoard)를 아래처럼 수정하고 목록의 전체 갯수를 리턴하는 API를 추가해주자!

페이지네이션 or 목록 전체 보기 API

  • HTTP 메서드: GET
  • url: /api/boards/list
  • 쿼리파라미터로 페이지 번호와 페이지 사이즈를 넘긴다!

목록의 전체 갯수 리턴 API

  • HTTP 메서드: GET
  • url: /api/board/count

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {

    ...
    
    static findAllBoard = async (req, res) => {
        const {page_number, page_size} = req.query;

        const options = {};
        options['select'] = ["id", "title", "content", "created", "updated"];
        options['order'] = {id: 'DESC'};

        // 쿼리 파라미터가 넘어오지 않으면 전체 목록 리턴!
        if (page_number && page_size) {
            options['skip'] = (page_number - 1) * page_size;
            options['take'] = page_size;
        }

        const boards = await getConnection().getRepository(Board).find(options);
        res.send(boards);
    }

    // 목록의 전체 갯수 리턴
    static countBoard = async (req, res) => {
        const total = await getConnection().getRepository(Board).count();
        res.send({total});
    }
}

라우팅 모듈

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";
 
const routes = Router();
 
routes.post('', BoardController.addBoard);
routes.get('/list', BoardController.findAllBoard);
routes.get('/:id', BoardController.findOneBoard);
routes.get('/count', BoardController.countBoard);
 
export default routes;
  • 테스트해보면 결과가 나오지 않는다. 왜 그럴까?
  • 앞에서 목록 하나 보기는 /api/board/:id 이고 카운트 api는 /api/board/count 이다.
  • 스프링에서는 에러가 나지 않았는데 여기서 에러가 나는 이유는 스프링에서는 id 부분을 숫자 타입이라고 지정했기 때문에 스트링 타입이면 count로 매핑하고 숫자이면 게시판 상세보기로 매핑했기 때문이다.
  • 여기서는 둘다 스트링으로 매핑되었기 때문에 /api/board/count 가 목록 하나 보기로 매핑이 되어버려서 결과가 나오지 않는다. 따라서 이 api를 목록 하나 보기보다 위로 올린다. 그러면 먼저 count를 매핑하고 없으면 아래로 내려가서 매핑하게 된다.

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";
 
const routes = Router();
 
routes.post('', BoardController.addBoard);
routes.get('/list', BoardController.findAllBoard);
routes.get('/count', BoardController.countBoard);
routes.get('/:id', BoardController.findOneBoard);
 
export default routes;


board 수정

  • HTTP 메서드: PUT
  • url: /api/board
  • 수정할 board의 id, title, content 값을 json 형식으로 body에 넣어서 입력한다!

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {

    ...
    
    static modifyBoard = async (req, res) => {
        const {id, title, content} = req.body;

        const updateOption = {};
        if (title) {
            updateOption['title'] = title;
        }
        if (content) {
            updateOption['content'] = content;
        }

        const result = await getConnection().createQueryBuilder().update(Board)
            .set(updateOption)
            .where("id = :id", {id})
            .execute();

        res.send(result);
    }
}

라우팅 모듈

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";

const routes = Router();

...

routes.put('', BoardController.modifyBoard);

export default routes;
  • 아래처럼 수정하는 API를 호출하고 

  • 해당 id 값을 가지는 board를 보여주는 API를 호출하면 아래와 같이 잘 반영이 되었다!

board 삭제

  • HTTP 메서드: DELETE
  • url: /api/board/:id
  • 삭제할 board의 id를 uri 파라미터로 넘겨서 DB에서 삭제한다!

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {

    ...
    
    static removeBoard = async (req, res) => {
        const {id} = req.params;

        const result = await getConnection()
            .createQueryBuilder()
            .delete()
            .from(Board)
            .where("id = :id", {id})
            .execute();

        res.send(result);
    }
}

라우팅 모듈

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";

const routes = Router();

...

routes.delete('/:id', BoardController.removeBoard);

export default routes;
  • id가 2인 board를 삭제하는 API를 호출하고

  • 모든 board를 보여주는 API를 호출하면 id가 2인 board가 삭제되었다!

전체코드

controller 구현

src/controller/BoardController.ts

// board entity
import {Board} from "../entity/Board";
import {getConnection} from "typeorm";

export class BoardController {
    static addBoard = async (req, res) => {
        const {title, content} = req.body;

        const board = new Board();
        board.title = title;
        board.content = content;
        const result = await getConnection().getRepository(Board).save(board);

        res.send(result);
    }

    static findAllBoard = async (req, res) => {
        const {page_number, page_size} = req.query;

        const options = {};
        options['select'] = ["id", "title", "content", "created", "updated"];
        options['order'] = {id: 'DESC'};

        // 쿼리 파라미터가 넘어오지 않으면 전체 목록 리턴!
        if (page_number && page_size) {
            options['skip'] = (page_number - 1) * page_size;
            options['take'] = page_size;
        }

        const boards = await getConnection().getRepository(Board).find(options);
        res.send(boards);
    }

    // 목록의 전체 갯수 리턴
    static countBoard = async (req, res) => {
        const total = await getConnection().getRepository(Board).count();
        res.send({total});
    }


    static findOneBoard = async (req, res) => {
        const {id} = req.params;

        const board = await getConnection().getRepository(Board).findOne({id});
        res.send(board);
    }

    static modifyBoard = async (req, res) => {
        const {id, title, content} = req.body;

        const updateOption = {};
        if (title) {
            updateOption['title'] = title;
        }
        if (content) {
            updateOption['content'] = content;
        }

        const result = await getConnection().createQueryBuilder().update(Board)
            .set(updateOption)
            .where("id = :id", {id})
            .execute();

        res.send(result);
    }

    static removeBoard = async (req, res) => {
        const {id} = req.params;

        const result = await getConnection()
            .createQueryBuilder()
            .delete()
            .from(Board)
            .where("id = :id", {id})
            .execute();

        res.send(result);
    }
}

 

라우팅 모듈

src/router/board.ts

import {Router} from "express";
import {BoardController} from "../controller/BoardController";

const routes = Router();

routes.post('', BoardController.addBoard);
routes.get('/list', BoardController.findAllBoard);
routes.get('/count', BoardController.countBoard);
routes.get('/:id', BoardController.findOneBoard);
routes.put('', BoardController.modifyBoard);
routes.delete('/:id', BoardController.removeBoard);

export default routes;