Express와 TypeORM을 이용한 API 만들기

Pien·2022년 9월 3일
1

위코드

목록 보기
5/10
post-thumbnail

저번주에 Node.js의 내장 모듈을 이용해 API 시스템을 구축했다.
내장 http 모듈을 사용해서 API시스템을 구축 했을 경우 if,else문을 통해 Method 와 EndPoint 를 지정해 줘야 하는데 이는 코드의 유지 보수성과 가독성을 크게 해친다.
오늘 사용할 Express 모듈은 2022년 09월 기준 주당 다운로드 횟수가 2500만건에 달하는 모듈이다.
Express 모듈을 사용하면 Method와 EndPoint 그리고 코드의 유지, 보수, 가독성이 크게 향상된다.
GitHub 코드 링크


Third-party module

오늘 사용할 서드파티 모듈은 cors, dotenv, morgan, typeorm, express 총 다섯가지를 사용한다.

CORS (Cross-Origin Resouce Sharing)

웹 생태계는 다른 출처의 리소스 요청을 제한하는 두 가지 정책이 존재한다. CORS와 SOP라는 것이다.
CORS 모듈은 프론트와 백엔드로 나눠진 웹 서비스를 서로 데이터를 주고 받기 위해 관련 요청의 조건을 완화시켜 서로간 통신을 원활하게 할 수 있도록 해준다.
CORS 모듈을 설치하지 않는다면 웹 브라우저 차원에서 서버 통신을 막아 통신을 사용할 수 없다.

dotenv

dotenv 모듈은 현재 디렉토리에 위치한 .env 파일로 부터 환경 변수를 읽어 낸다.
.gitignore파일로 .env 를 등록해 환경 변수를 기록 해두면, 코드 내에서 나의 환경 변수를 노출하지 않고 코드를 작성할 수 있다.

morgan

morgan 모듈은 Node.js의 기본적으로 내장되어 있지 않은 네트워크 로그를 관리하기 위해 사용한다.
morgan모듈도 주당 다운로드 횟수가 350만건에 달하는 인기가 많은 모듈이다.

typeorm

ORM(Object-relational mapping)은 객체지향 프로그래밍과 관계형 DB사이의 호환되지 않는 데이터를 중간에서 연결해주는 중간 매개체 역할을 한다.
ORM중 하나인 typeorm을 사용하면 선언문, 할당, 종료 등과 같은 부수적인 코드가 사라져 Raw 쿼리문을 사용하지 않아도 된다는 장점이 있지만, 나는 쿼리문에 익숙해지기 위해 typeorm 문법이 아닌 Raw 쿼리문을 사용해 API를 작성할 예정이다.

Express

Express 모듈은 빠르고 자유롭고 가벼운 웹 프레임 워크이다. 라고 공식문서에 정의되어 있습니다.
Express는 Node 개발자라면 대 다수가 채택하는 프레임워크 이며, 라우팅과 로직을 모듈화 하기 쉽게 만들어 주며 유지 보수가 쉬워지며 가독성이 좋은 코드를 만들어 준다.


서버 실행

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const { DataSource } = require('typeorm');

const app = express();
const PORT = process.env.PORT;

const appDataSource = new DataSource({
  type: process.env.TYPEORM_CONNECTION,
  host: process.env.TYPEORM_HOST,
  port: process.env.TYPEORM_PORT,
  username: process.env.TYPEORM_USERNAME,
  password: process.env.TYPEORM_PASSWORD,
  database: process.env.TYPEORM_DATABASE
})

appDataSource.initialize()
    .then(() => {
      console.log("Data Source has been initialized!")
    })
    .catch((err) => {
      console.error("Error during Data Source initialization", err)
      appDataSource.destroy()
    })

app.use(cors());
app.use(morgan('dev'));
app.use(express.json());

const start = async () => {
  try {
    app.listen(PORT, () => console.log(`Server is listening on ${PORT}`));
  } catch (err) {
    console.error(err);
  }
}

start();

서버를 시작하기 위한 기본 요소이다.
총 5개의 모듈을 require 를 통해 인가 받고, express, cors, morgan 미들웨어를 실행, .env 파일에 환경 변수를 읽어 PORT 와 DB를 읽어 온 후 에러가 없는 경우, start() 함수를 실행해 서버를 구동 한다.


CRUD API

이번에 구현한 API CRUD는 전체 유저 조회, 전체 게시글 조회, 특정 유저의 게시글 조회, 유저 회원가입, 게시글 작성, 게시글 수정, 게시글 삭제 에 대한 기능을 구현했다.

GET

전체 유저 조회

app.get("/user", async (req,res) => {
  const user = await appDataSource.query(
    `SELECT
      users.name,
      users.email,
      users.profile_image
    FROM users`,
    );
    res.status(200).json({data : user});
});

메소드 :GET, 엔드포인트 : /user 인 경우, 전체 유저 조회 API가 작동한다.
쿼리문을 통해 users 테이블에 접근해 name, email, profile_image 데이터를 user 배열에 저장 한 뒤, 저장한 데이터를 반환 해 준다.

특정 유저의 게시글 조회

app.get("/user/post/:userId", async (req,res) => {
  const userId = req.params.userId;
  
  const user = await appDataSource.query(
    `SELECT
      users.id as userId,
      users.profile_image as userProfileImage
    FROM users
    WHERE users.id = ${userId};`,
  );
  const post = await appDataSource.query(
    `SELECT
      posts.id as postingId,
      posts.title as postingImageUrl,
      posts.content as postingContent
    FROM posts
    WHERE posts.user_id = ${userId};`,
  );
  user[0].posting = post;
  const result = user[0];
  res.status(200).json({data:result})
})

메소드 : GET, 엔드포인트 : /user/post/:userId 인 경우, :userId 에 해당하는 유저의 게시글 조회 API 가 작동한다.
유저 데이터 안에 포스팅 데이터를 넣기 위해 SELECT 쿼리 문을 두개 사용해, 각 각 유저의 데이터, 포스트의 데이터를 userpost 변수에 저장해 주었다.
쿼리문은 데이터를 배열로 반환하고, WHERE 문으로 id를 지정해 주었기 때문에 배열에는 데이터가 첫번째 배열에만 들어 있다. user 배열의 첫번째 값에 접근해 post 변수를 value 값으로 posting 이라는 key 값을 지정해 넣어주고, 완성 된 데이터를 반환 해 준다.

실행 예시

POST

유저 회원 가입

app.post("/user", async (req, res, next) => {
  const { name, email, profileImage, password } = req.body;
  await appDataSource.query(
    `INSERT INTO users(
      name,
      email,
      profile_image,
      password
    ) VALUES (?, ?, ?, ?);`,
    [ name, email, profileImage, password ]
    );
  res.status(201).json({message : "userCreated"});
})

메소드 : POST, 엔드포인트 : /user 인 경우, 유저 회원가입 API가 작동한다.
name, email, profileImage, password 네가지 값을 받아 쿼리 문을 통해 데이터를 DB에 넣어준다.
그리고 201 status 코드와 userCreated 문자를 반환 해준다.

PATCH

게시글 수정

app.patch("/post/:postId", async (req, res, next) => {
  const postId = req.params.postId;
  const { title, content, userId } = req.body;
  await appDataSource.query(
    `UPDATE posts SET
      title = ?,
      content = ?,
      user_id = ?
    WHERE id = ${postId}`,
    [ title, content, userId ]
  );
  const post = await appDataSource.query(
    `SELECT
      users.id as userId,
      users.name as userName,
      posts.id as postingId,
      posts.title as postingTitle,
      posts.content as postingContent
    FROM posts INNER JOIN users on users.id = posts.user_id where posts.id like ${postId};`,
    );
    res.status(200).json({data : post[0]});
})

메소드 : PATCH, 엔드포인트 : /post/:postId 인 경우, 게시글 수정 API 가 작동한다.
:postId 값에 해당하는 포스트를 posts 테이블에 UPDATE 쿼리문으로 데이터를 업데이트 한 뒤, 새로운 SELECT 쿼리 문으로 posts 테이블에 수정한 게시글 정보와 해당하는 유저 정보를 출력 해 준다.

실행 예시

DELETE

게시글 삭제

app.delete("/post/:postId", async (req, res, next) => {
  const postId = req.params.postId;
  await appDataSource.query(
    `DELETE FROM posts
    WHERE posts.id = ${postId}`);
    res.status(204).json({message : "postingDeleted"});
})

메소드 : DELETE, 엔드포인트 : /post/:postId 인 경우, 게시글 삭제 API가 작동한다.
:postId에 해당하는 포스트를 DELETE 쿼리 문을 통해 DB에서 삭제 후 postingDeleted 메시지를 출력 해 준다.

마치며

이번에 DB와 API를 연결해보며 많은 시행착오와 오류를 겪으며, 좀 더 발전한 개발자가 된 느낌이 든다.
Express모듈을 사용해 보며, 이전에 Node.js 의 기본 모듈을 사용해 API를 만들었을 때 보다 훨씬 구조적이며, 코드가 깔끔해 졌다.
하지만, 이런식으로 코드를 파일 하나로 관리 하면 코드의 유지 보수, 가독성, 확장성 등의 문제가 있다.
그래서 다음엔 오늘 만들어둔 코드를 기능별로 분리해 좀더 유지 보수가 좋은 코드로 만들고자 한다.

0개의 댓글