Express와 TypeORM을 활용한 CRUD API 만들기
Express를 도입하며 자주 등장하게 될 req | res 이 두 단어는 ‘요청’과 ‘응답’으로 쉽게 번역될 수 있는 request와 request를 의미하는 변수명입니다. 여기서 req는 HTTP 통신시 요청에 대한 정보를 담는 객체를 의미합니다. 그리고 이 req에 상응하여 응답에 대한 정보를 담는 객체가 바로 res라고 표현될 수 있습니다. 그리고 백엔드 서버 입장에서 클라이언트측에서 전달한 여러 메타 정보를 확인해야할 때는 req 객체에, 백엔드 서버로서 내보내야하는 다양한 메타 정보들을 담아내야 할 때는 res 객체에 접근하게 됩니다. 이제부터 이 둘이 품고 있는 다양한 속성 및 메소드들을 살펴보며 세부 역할과 기능을 파악해보겠습니다.
req - 요청 객체 (Request)
request 객체에 존재하는 다양한 메소드들을 소개하겠습니다. (자주 사용하는 것은 진하게~)
- req.params : 이름 요청시 넘겨져온 path 파라미터를 담습니다.
- req.query : GET 방식으로 넘어오는 query string 파라미터를 담고 있습니다.
- req.body : POST 방식으로 넘어오는 파라미터를 담고있다. HTTP의 BODY 부분에 담겨져있는데, 이 부분을 파싱하기 위해 body-parser와 같은 패키지가 필요하다. 과거에는 body-parser를 꼭 별도로 설정했어야 했으나 express에 그 기본 기능이 내장되어 이제는 불필요하다. app.use(express.json()) 코드가 실행되면 저절로 parsing 해주는 기능이 있습니다.
- req.route : 현재 라우트에 관한 정보를 담고 있습니다.
- req.headers : HTTP의 Header 정보를 가지고 있습니다.
- req.ip : 클라이언트의 IP Address를 호출합니다.
- req.path : 클라이언트가 요청한 경로. 프로토콜, 호스트, 포트, 쿼리스트링을 제외한 순수 요청 경로입니다.
- req.host : 요청 호스트 이름을 반환하는 간단한 메서드. 조작될 수 있으므로 보안 목적으로는 사용되어서는 안됩니다.
- req.protocol : 현재 요청의 프로토콜 (http / https 등)
res - 응답 객체 (Response)
response 객체에 존재하는 다양한 메소드들을 소개하겠습니다. (자주 사용하는 것은 진하게~)
- res.status(code) : HTTP 응답 코드를 설정합니다.
- res.set(name, value) : 응답 헤더를 설정합니다. 일반적으로 직접 사용자가 쓸 일은 드문 편입니다.
- res.send(body), res.send(status, body) : 클라이언트에 응답을 보내며 상태 코드에 대한 추가 여부는 선택사항입니다. 기본 콘텐츠 타입은 text/html이므로 text/plain을 보내려면 res.set(‘Content-Type’, ‘text/plain’)을 먼저 호출 해야합니다. JSON 형태의 전송은 바로 다음 이어지는 res.json을 사용합니다.
- res.json(json), res.json(status, json) : 클라이언트로 JSON 형태의 데이터를 보냅니다.
- res.type(type) : Contents-Type 헤더를 설정할 수 있는 간단한 메서드입니다.
- res.sendFile(path, [options], [callback]) : path의 파일을 읽고 해당 내용을 클라이언트로 전송한다.
[app.xxx( )] 의 정체
‘app’은 express가 프레임워크로서 기본으로 제공하는 다양한 내부 기능 (Application: 공식문서 내 영문명칭)을 담아내기 위해 사용하는 객체입니다. 그리고 변수명 네이밍 컨벤션에 의거, Application의 가장 세 앞글자를 본 따 ‘app’이라는 명칭으로 부르게 되었습니다. 소스코드 내에서 이 ‘app’이 가장 먼저 등장하는 시점은 바로 최상위단에서 함수 형태의 express를 불러오는 순간으로, 이는 곧 express가 직접 실행되는 시점을 의미합니다.
const express = require('express'); // --- (1)
const app = express(); // --- (2)
여기서 사용되는 require 함수는 외부에 있는 express모듈에 내장된 함수들을 가져다가 쓰는 함수입니다.(이게 정확한지 모르겠어요ㅎㅎ)
위 소스 코드의 내용은 (1) require 메소드를 통해서 “express 모듈을 임포트하여 객체를 생성하고 express 변수가 참조하도록 하는 행위”와 (2) "express()" 함수를 호출하고, app 이라는 변수안에 담는 행위를 포함하고 있습니다. 다시 말해서, ‘express()’ 라는 일종의 클래스 기능을 ‘app’이라는 새로운 변수안에 담아 객체 형태로 선언하는 것 입니다.
위와 같이 선언한 객체는 그 내부에 존재하는 다양한 메소드를 활용할 때 빈번히 쓰이게됩니다. 왜냐하면 express()라는 함수를 통해서 생성된 app이라는 객체는, 전체 API 서버의 기능을 정의하고 서버를 실행시키는 주 객체로 활용되기 때문입니다. 즉, app 객체와 상호작용 하는 과정에서 우리가 의도하는 API 기능이 실행/구현되는 것이라고 정의할 수 있습니다.
express가 제공하는 Application 기능은 대표적으로 아래와 같습니다.
- HTTP 요청 라우팅 기능
- 미들웨어 상세설정
- HTML 렌더링 기능
- 템플릿 엔진 렌더링 기능
우리는 위 기능중 백엔드 개발자로서 프론트와의 네트워크 통신을 위해 필수로 활용해야하는 ‘app.use()’와 ‘app.HTTPmethod()’ 이 두가지 메소드를 중점적으로 살펴보겠습니다. 아래 예시를 참고하며 두 메소드들의 동작 원리를 살펴보겠습니다.
app.use()
Express는 미들웨어 함수의 호출/실행으로 이루어지는 프레임워크라고 표현할 수 있습니다. 여기서 미들웨어 함수란, ‘req’ 객체, ‘res’ 객체, 그리고 미들웨어 함수를 호출시키는 ’next’를 매개변수로 받는 함수를 의미합니다.
미들웨어는 “함수와 함수 사이에 존재하여 이 둘을 연결 짓는 또 다른 함수” 라고 생각하면 된다고 합니다...
app.use()는 일종의 미들웨어를 추가하는 함수입니다. 기존에 app.use()의 기본 디폴트 path는 '/'가 있어야합니다. 하지만 저희가 앞서 추가한 cors나 morgan, express.json 같은 경우에는 따로 path를 지정하지 않았기 때문에 클라이언트가 그 어떠한 요청을 보내더라도 app.use는 매번 호출되게 됩니다.
app.httpMethod()
app.httpMethod()는 외부에서 들어오는 HTTP 네트워크 요청을 라우팅합니다. HTTP 네트워크 요청이란, CRUD 기능에 상응하는 세부 HTTP 메서드 (GET, POST, PUT, DELETE) 들을 의미합니다. 이를 express 소스코드 내에서 사용하려면 [app.get(), app.post(), app.put(), app.delete()]등 의 형태로 작성하면 됩니다.
app.delete()는 body값을 받아올 수 없기 때문에 내용 추가 기능을 사용할 수 없다고 합니다(직접해보지는 않았습니다...)
Express와 TypeORM이 적용된 CRUD API 만들기
데이터베이스 테이블 생성
dbmate를 이용하여 migration 파일들을 생성하고 생성된 파일을 이용하여 mysql에 table header 속성들을 설정해줍니다.
table들을 다 설정한 이후 $dbmate up 명령어를 실행하게 되면 table header 속성들이 mysql에 저장되는 것을 확인 할 수 있습니다. (물론 환경설정을 마치고 해야 등록됩니다.)
CRUD API 작성하기
crud api를 작성하기 위해서는 mysql query문을 알아야합니다. (각자 알아서 공부해보기 추후 업로드 예정...)
예를 들어 상기에 만든 books라는 table에 http request를 받은 데이터를 넣으려고 한다면!
(url설정은 자유입니다)
이런 식으로 구현할 수 있습니다. 분석을 해볼까요?
1. app.post('/books', async (req, res)
메소드라는 것을 확인 할 수 있습니다. 엔드포인트가 /books이고, async & await를 이용해서 비동기적인 작동을 하게 만들었습니다.
async & await를 사용하는 이유는 비동기적으로 작동함으로 구현 속도를 빠르게 하기 위해서도 있겠지만 함수가 실행될 때 내부에 있는 response 문이 퀴리문보다 빠르게 시작하지 않게 하기 위해서 await를 사용해서 이 함수를 실행하기 전에 다른 것을 기다려라? 라는 의미같습니다. 글을 쓰면서 생활코딩님의 영상을 보고 있는데 여러분들도 함께 보고 공부하시죠 ㅎㅎ
네! 공부를 하고 왔는데요. 비동기적으로 실행하기 위해서 사용하는 것이 맞습니다.
2. const { title, description, coverImage} = req.body
이 내용은 request로 받은 body 값을 구조분해 할당하는 것입니다. 만약 body 값을 json으로 받아오게 된다면
{ "title":"부자 아빠 가난한아빠", "description":"부자되는 방법", "coverImage":"richdad.jpg" }
뭐 이렇게 넣어주면 각각 구조분해할당이 되겠죠?
3. await myDataSource.query( `INSERT INTO books( title, description, cover_image ) VALUES (?, ?, ?); `, [ title, description, coverImage ] );
받아온 body 값을 query문 안에 집어 넣는 함수입니다. 저기 보이는 ?들 안에 순차적으로 data가 들어가게 될 겁니다.
4. res.status(201).json({ message : "successfully created" });
응답코드 201로 하여 메세지를 보내는 것으로 마무리 했습니다.
이렇게 퀴리문만 잘 사용한다면 데이터를 뽑아오는 것은 자유로워 질 겁니다. (퀴리문도 무궁무진하더군요....)
https://www.youtube.com/watch?v=TAyLeIj1hMc&list=PLuHgQVnccGMBVQ4ZcIRmcOeu8uktUAbxI&index=1