Express 란 ?
Express.js, 또는 간단히 익스프레스는 Node.js를 위한 웹 프레임워크의 하나로, MIT 허가서로 라이선스 되는 자유-오픈 소스 소프트웨어로 출시되었다. 웹 어플리케이션, API 개발을 위해 설계되었다. Node.js의 사실상의 표준 서버 프레임워크로 불리고 있다.
본격적으로 서버를 구축하기 위해서는 이 express 프레임워크를 사용해야한다.
express를 사용하기 위해서는 패키지 매니저를 먼저 설치해야된다.
패키지 매니저로는 yarn을 사용한다.
Yarn 이란?
yarn은 Node.js 자바스크립트 런타임 환겨을 위해 페이스북이 2016년 개발한 소프트웨어 패키지 시스템이다. npm 패키지 관리자의 대안 으로서 Yarn은 페이스북, Exponent, 구글, Tilde의 협업으로 대형 코드의 일관성, 보안, 성능 문제를 해결하고자 개발되었다.
기존에 사용하던 npm을 보완해서 새롭게 등장한 패키지 매니저이다.
아주 간단하다.
//설치 진행 (오류 발생할 경우는 대부분 권한.. 명령문 앞에 sudo 붙여주면 해결)
> npm i -g yarn
//설치 잘 되었는지 버전 확인
> yarn --version
버전 확인 오류 없이 잘 된다면 성공 !!
1) yarn 세팅
작업 경로에 이제 express로 서버 작업을 할 폴더를 하나 만들어준다.
(나는 임의로 express-start
라는 이름의 폴더를 만들어서 그 곳에서 작업해보겠댱)
그리고 이 디렉토리로 이동해서 터미널에 명령어 입력 !
> yarn init
그럼 이제 터미널에 서버에 대한 간단한 사전 설정을 하도록 다라라락 뜨게 된다.
name
은 원하는대로 적어주면 되고 나머지는 엔터 눌러주면 된다.
yarn init v1.22.18
question name (seminar2-express): express-example
question version (1.0.0):
question description:
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
✨ Done in 12.78s.
이 과정까지 해주면 디렉토리에 package.json
파일이 자동적으로 생성된다.
💡 설치한 패키지의 버전을 관리하는 파일이 바로
package.json
이다.
따라서 노드 프로젝트를 시작하기 전에는 폴더 내부에 무조건 package.jos 부터 만들고 시작해야 한다.
2) Express 설치
앞에서 사용했던 패키지 매니저 yarn을 이용해서 이제부터 사용할 express 패키지를 설치하면 된다.
yarn add express && yarn add -D @types/node @types/express nodemon
@types/node
와 같이 @types
가 붙은 것은 typescript용 모듈을 설치하는 것이다.
nodemon
은 서버 코드에서 수정 사항이 생겨도 개발자가 직접 재가동하지 않도록 자동으로 재시작 해주는 좋은 모듈이다.
nodemon은 개발용으로 사용하는 것을 권장한다. 배포 후에는 서버 코드가 빈번하게 변경될 일이 없으므로 nodemon을 사용하지 않아도 된다.
3) tsconfig.json 파일 생성
Typescript 기반의 서버를 구축하기 위해서는 필수적인 파일이다.
TS 파일을 JS 파일로 컴파일해서 실행한다는 것은 1차 세미나에서 배웠다.
여기서 tsc
명령어와 ts-node
의 차이까지 배웠었다.
tsc
: Production의 컴파일 단계에서 주로 사용
ts-node
: 간단한 개발 단계에서의 컴파일에 주로 사용 (build 파일이 필요 없기 때문에)
tsc
의 설정 즉, TS를 JS로 컴파일하는 옵션을 설정할 수 있는 파일이 바로 tsconfig.json
파일이다.
이어서 다음 명령어를 현재 작업 폴더 내의 터미널에서 입력해준다.
> tsc --init
이후에 tsconfig.json
파일이 생성되는데 이 파일을 아래의 내용으로 바꿔준다.
// tsconfig.json
{
"compilerOptions": {
"target": "es6", //? 어떤 버전으로 컴파일
"allowSyntheticDefaultImports": true, //? default export가 없는 모듈에서 default imports를 허용
"experimentalDecorators": true, //? decorator 실험적 허용
"emitDecoratorMetadata": true, //? 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원
"skipLibCheck": true, //? 정의 파일 타입 체크 여부
"moduleResolution": "node", //? commonJS -> node 에서 동작
"module": "commonjs", //? import 문법
"strict": true, //? 타입 검사 엄격하게
"pretty": true, //? error 메시지 예쁘게
"sourceMap": true, //? 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성
"outDir": "./dist", //? 트랜스 파일 (.js) 저장 경로
"allowJs": true, //? js 파일 ts에서 import 허용
"esModuleInterop": true, //? ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용
"typeRoots": [
"./src/types/express.d.ts", //? 타입(*.d.ts)파일을 가져올 디렉토리 설정
"./node_modules/@types" //? 설정 안할시 기본적으로 ./node_modules/@types
]
},
"include": [
"./src/**/*" //? build 시 포함
],
"exclude": [
"node_modules", //? build 시 제외
"tests"
]
}
해당 설정값은 다 외우지 않아도 된다. 주석을 꼼꼼하게 정말 잘 달아주신 sopt 30th 서버 팟장님께 감사드린다...
최고인 것 같다..👍
현재 디렉토리에 src
폴더를 만들고 그 내부에 index.ts
파일을 만들어주자.
이는 서버의 역할을 하는 ts 파일이다. 파일명으로는 app.ts 도 많이 쓰는 것 같다.
//index.ts는 user의 request가 가장 먼저 도달하는 ts이다.
import express, {NextFunction, Request, Response } from "express";
const app = express(); // express 객체를 받아옴
const PORT = 3000; // 사용할 port를 3000번으로 설정
app.use(express.json()); // express 에서 request body를 json 으로 받아오겠다.
app.use("/api", require("./api")); // use -> 모든 요청
//localhost:3000/api -> 앞으로 만들어줄 api 폴더의 ts 파일들과 연결하는 url
//ex) localhost:3000/api/user -> api/user.ts 파일과 연결
//* HTTP method - GET
app.get("/", (req: Request, res: Response, next: NextFunction) => {
res.send("2차 세미나 안 듣고 과제 하려니까 죽을 맛이다...");
});
app.listen(PORT, () => {
console.log(`
###############################################
🛡️ Server listening on port: ${PORT} 🛡️
###############################################
`);
}); // 3000 번 포트에서 서버를 실행하겠다!
짚고 넘어가야 할 부분은 app.use("/api", require("./api")); 인데, 여기서 바로 미들웨어 라는 개념이 등장하기 때문이다 !
📌 여기서 잠깐 ! 미들웨어 ?
익스프레스의 핵심은 미들웨어 이다. 요청과 응답의 중간에 위치하여 미들웨어라고 한다. 뒤에 나오는 라우터와
(에러 핸드러)또한 미들웨어의 일종이다. 미들웨어는 요청과 응답을 조작하여 기능을 추가하기도 하고, 나쁜 요청은 거르기도 한다.
이런 미들웨어는 app.use 와 함께 사용된다.
app.use((req: Request, res: Response, next: NextFunction) => {
res.send("모든 요청에서 다 실행됩니다 ...");
});
app.get("/", (req: Request, res: Response, next: NextFunction) => {
console.log("GET / 요청에서만 실행됩니다 ...");
next();
}, (req: Request, res: Response) => {
throw new Error("에러는 에러 처리 미들웨어로 갑니다 ...")
});
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err);
res.status(500).send(err.message);
});
미들웨어는 위에서부터 아래로 순서대로 실행되면서 요청과 응답 사이에 특별한 기능을 추가할 수 있다.
여기서 next 라는 세번째 매개변수는 다음 미들웨어로 넘어가는 함수이다. next를 실행하지 않으면 다음 미들웨어가 실행되지 않는다.
주소를 첫번째 인수로 넣어주지 않는다면 미들웨어는 모든 요청에서 실행되고, 주소를 넣는다면 해당하는 요청에서만 실행된다고 보면 된다.
app.use(미들웨어)
: 모든 요청에서 미들웨어 실행app.use('/abc', 미들웨어)
: abc로 시작하는 요청에서 미들웨어 실행app.post('/abc', 미들웨어)
: abc로 시작하는 POST 요청에서 미들웨어 실행app.use나 app.get 같은 라우터에 미들웨어를 여러 개 장착할 수도 있다. (router의 개념은 밑에서 자세히 설명한다.) 다만 이때도 next를 호출해야 다음 미들웨어로 넘어간다.
현재 app.get('/')
의 두번째 미들웨어에서 에러가 발생하고, 이 에러는 그 아래에 있는 에러 처리 미들웨어에 전달된다.
에러 처리 미들웨어 는 매개변수가 err, req, res, next
로 반드시 4개를 써줘야 한다. 에러 처리 미들웨어를 직접 연결하지 않아도 기본적으로 익스프레스가 에러를 처리하지만 실무에서는 직접 연결해주는 것이 좋다.
src
폴더에서 나가서 작업 디렉토리 (express-start
폴더) 에서 nodemon.json
을 생성한다.
{
"watch": ["src", ".env"],
"ext": "js,ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "ts-node --transpile-only ./src/index.ts"
}
위와 같이 작성해준 뒤, 저장해준다. 위에서 정해준 설정값을 간단히 설명하자면 아래와 같다 !
watch
exec
ext
ignore
그 다음으로는 package.json
파일을 살짝 수정해주어야 한다.
개발환경에서는 실제 실행을 도와줄 명령 스크립트를 프로젝트의 package.json 의 run script에 등록해놓는다.
위의 사진에 드래그 해놓은 부분을 추가해주면 된다. 이렇게 package.json
파일까지 수정하고 나면 아래와 같은 명령어로 실행할 수 있다.
yarn run dev
: nodemon을 기반으로 서버 실행
yarn run build
: TS 프로젝트를 JS로 빌드
그렇다면 서버를 실행해보자 !! 터미널에 위의 명령어인 yarn run dev
를 치게 되면, 위와 같이 nodemon
을 통해서 서버를 잘 구동시킬 수 있다.
그리고 이제 localhost:3000으로 접속해보면 서버가 아주 잘 돌아가고 있댱 ! 😎
nodemon의 장점이 무엇이었는가,, 서버 코드에서 수정 사항이 생겨도 개발자가 직접 재가동하지 않도록 자동 재시작을 해주는 아이다.. 그렇다면 코드를 바꿔볼까 ?? 위와 같이 변화를 아주 잘 감지해서 서버를 자동으로 재시작해준다 ! 물론 localhost:3000도 새로 고침하면 변동 사항이 적용되어 보인다 !
이후 이 서버를 실제로 배포할 때는 TS 파일이 아닌 JS 서버 파일이 필요하다. 이 때 build 폴더를 생성해줄 yarn run build
를 사용하면 되는 것이다.
그럼 작업 디렉토리에 js 트랜스파일과 sourceMap 파일이 생성될 것이다 !
위에서 살짝 언급했었던 라우터,, 라우터가 대체 뭐야 ❓❓❓
클라이언트의 요청 경로(path)를 보고 이 요청을 처리할 수 있는 곳으로 기능을 전달해주는 역할을 한다. 이러한 역할을 라우팅이라고 하는데, 애플리케이션 엔드 포인트 (URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식을 의미한다.
예를 들어, 클라이언트가 /users
경로로 요청을 보낸다면 이에 대한 응답 처리를 하는 함수를 별도로 분리해서 만든 다음 get()
메소드를 호출하여 라우터로 등록할 수 있다.
말로 하면 이해가 더 어려운거 같다.. 직접 실습을 통해 라우터를 이해해보자 !
하나의 서버를 직접 구축하는 예시로 설명을 하면 이해가 쉬울 것 같아서,
이번 SOPT 2차 세미나의 도전 과제로 express 에서 router를 구현하는 방법을 설명하고자 한다 !
👇 이번 2차 세미나의 도전 과제의 내용은 다음과 같았다
📌 Express 프로젝트를 만들고, 다음 5개의 라우팅을 구성해보기
/api/user
/api/blog
/api/comment
/api/movie
/api/members ( 1차 세미나때 작업했던 파트원들 정보를 Response )
(라우팅 형식은 자유입니다. 여러분의 입맛에 맞게 작성하시고 코드 리뷰팀과 이야기 나눠보세요!)
이 과제를 통해 Express에서 라우팅 하는 방법에 대해 정리하고자 한다.
Express를 사용하는 이유 중 하나가 Express에서는 라우터를 분리할 수 있는 방법을 제공하기 때문에 라우팅을 깔끔하게 관리할 수 있다는 점이다.
또한 라우터 분리 구조로 구현하면, 쉽게 위치를 찾고 유지 보수에 용이하다.
- api 폴더 : 요청/응답에 대한 로직을 수행하는 api를 묶어놓은 폴더
- router 폴더 : api 폴더에 있는 api들의 라우팅을 담당하는 폴더
우리는 지금 5개의 라우팅을 하는 것이 목표이다.
그렇다면 src/index.ts 파일에서 이 5개의 라우터를 각각 연결해줘야 할까 ?
❗️ NO ❗️
간단하게 하나의 /router/index.ts 파일을 만들어서 여기서 각 라우터들로 분기를 한번에 해주면 더 깔끔하게 구현할 수 있다.
☑️ 1. src / index.ts index.ts 파일에서 .use
함수를 이용해서 /api
로 시작하는 요청에서 indexRouter 라우터 객체를 연결하도록 구현했다.
여기서 indexRouter
객체는 ./router/index
에서 export 하는 라우터 모듈이다.
indexRouter 객체가 뭔데 ?! ./router/index.ts
을 살펴보자.
☑️ 2. src / router / index.ts
우리는 router 폴더 안에 총 5개의 라우터 파일을 만들 것이다. 이를 이 index.ts 파일에서 모두 import 한 뒤 라우터 객체를 하나 만들고, .use
함수를 사용하여 각 라우터가 연결될 요청 주소와 라우터 객체를 넘겨준다.
그렇게 되면
/api/user
로 시작하는 요청은 userRouter 객체를 연결하고 ... /api/blog
로 시작하는 요청은 blogRouter 객체를 연결하고 ... (이하생략) ...
💡 다시 말해, /api
를 요청하면 이 indexRouter가 연결되고, 여기서 이제 각 5개의 라우터로 분기되는 것이다 !
이렇게 5개로 분기시키는 라우터를 마지막에 export router로 모듈화 해서 내보내므로 src/index.ts
에서 indexRouter
로 받을 수 있는 것이다 !
☑️ 3. src / router / userRouter.ts
이제 각 라우터 파일을 봐보자. 5개의 형식은 다 똑같아서 userRouter.ts 만 보면 이해가 될 것 같다 !
여기서는 이제 이 라우터가 수행할 api와 연결을 해주어야 한다.
HTTP 메소드에 따라 수행하는 api가 다를 것이므로 여기서 여러 개의 HTTP 메소드 (.get(), .post(), .put(),,, 등등) 을 연결해주면 된다.
우리는 이런 api들도 따로 폴더를 만들어 분리해서 구현할 것이기 때문에 3번 line처럼 모듈을 import해서 사용한다.
이후 HTTP 메소드에 알맞는 라우팅 메소드 (여기서는 .get()
)에 라우팅 경로와 수행할 api 모듈을 넘겨주여 연결해준다. -> 7번 코드라인
📌 잠깐 ! 라우팅 경로 ?
라우팅 경로는 요청 메서드와의 조합으로 요청이 이루어질 수 있는 엔드포인트를 정의한다. 라우트 경로는 문자열, 문자열 패턴, 정규식이 될 수 있다.
정리하자면 !
서버 개발에서는 통상적으로 엔드포인트를 정의하고, 해당 엔드포인트의 요청에 대해 어떻게 응답할지 정하는 것을 라우팅 이라고 말한다.
이 때 엔드포인트는 uri로, 요청 방식은 http 메서드 가 되는 것이다.
단순히 라우팅 경로 라고 표현한다면 엔드포인트만이 경로에 해당하는 정보이고, 그 경로에서 들어오는 "어떤 요청"에 대한 응답인지가 http 메소드가 되는 것이다.
마지막으로 이 라우터를 export 해서 indexRouter에서 이 라우터 모듈을 import 해서 분기할 수 있게 구현하면 되는 것이다.
결국 라우터는 각 요청 메서드와 요청 경로가 있을 때 어떤 로직을 수행하면 되는지 중간에서 연결해주는 것이라고 생각할 수 있겠다.
이런 로직을 api 라고 하는데 이를 구현하는 ts 파일들도 따로 api 폴더를 구분해서 구현하는 것이 좋다 !
이것도 함수 내용만 변경이 있을 뿐 전체적인 구조는 같기 때문에 api/movie.ts 만 설명하면 될 것 같다 !
☑️ src / api / movie.ts
코드를 보면 getMovie
라는 함수를 하나 만들게 된다. 이 때 Request 객체와 Response 객체를 파라미터로 받는다.
여기서 이 getMovie는 프론트엔드가 요청한 movie의 정보를 json 형태로 보내주는 api 이다. 따라서 Response 객체, 즉 res에 json 형태로 정보를 담아 return 해주도록 구현하는 것이다.
이후 이 함수를 모듈화 하여 export하고 이를 위에 설명했듯이, 각 라우터 파일에서 import 해서 라우팅 메소드에 넘겨주면 되는 것이다.
다음과 같이 라우팅이 잘 된 것을 확인 할 수 있다 !!!!