Express로 먼저 서버 기초 구현해 보기
이번에는 Express를 사용해 아주 간단한 서버를 구성해 보았다. 단순히 화면에 텍스트를 띄우는 것부터, 존재하지 않는 페이지에 대한 처리까지 직접 코드를 따라 치면서 서버가 요청을 받고 응답을 주는 흐름을 이해해 보려고 한다.
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello, World!'));
app.get('/post', (req, res) => '<h1>Home Page</h1>');
app.get('/User', (req, res) => '<h1>User Page</h1>');
app.use((req, res) => {
res.status(404).send('<h1>Page not found</h1>');
})
app.listen(3000, () => {
console.log('server on localhost:3000')
})
코드 흐름 해석하기
이번 코드에서 먼저 본 부분은 express()를 호출해 app 객체를 만드는 것이다. 이 객체가 바로 서버의 핵심 역할을 하게 된다.
그다음으로는 라우팅 설정을 따라갔다. app.get(경로, 콜백함수) 형태로 작성되어 있는데, 사용자가 특정 경로로 GET 요청을 보냈을 때 서버가 어떻게 응답할지 정의하는 부분이다. 예를 들어 루트 경로(/)로 접속하면 'Hello, World!' 문자열을 응답으로 보내도록 했다. 코드를 살펴보면 /post 경로와 /User 경로도 각각 추가되어 있는데, 이때 res.send() 없이 바로 문자열을 반환하도록 작성된 부분도 눈에 띄었다.
마지막으로 눈여겨본 것은 404 처리다. app.use()를 사용해 위에서 정의한 경로 어디에도 해당하지 않는 요청이 들어올 경우, res.status(404)를 통해 '페이지를 찾을 수 없다'는 응답을 내려주도록 예외 처리를 해두었다.
모든 라우팅 설정이 끝난 후에는 app.listen(3000)을 통해 서버가 3000번 포트에서 요청을 받을 준비를 마치하게 된다.
Express에서 NestJS 구조로 넘어가기
Express에서는 app.get() 같은 메서드로 직접 라우팅을 연결했다면, NestJS에서는 이 구조가 클래스와 데코레이터(Annotation) 형태로 바뀐다. 먼저 서버를 시작하는 진입점 코드를 보았다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
이 부분을 보면서, Express에서 express()로 앱을 만들고 listen()을 호출했던 흐름과 비슷하다는 것을 알 수 있었다. 다만 환경 변수(process.env.PORT)에 값이 없다면 기본값으로 3000번 포트를 사용하도록 ?? 연산자가 쓰인 부분이 눈에 띄었다.
컨트롤러에서 라우팅 처리하기
서버가 켜지는 방식을 확인했으니, 이제 실제로 경로에 따라 응답을 주는 코드를 보자. NestJS에서는 Controller라는 클래스 안에 라우팅을 묶어서 관리한다.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get() // '/'
getHome() {
return 'Home Page';
}
@Get()
getPost() {
return 'Post Page';
}
@Get()
getUser() {
return 'User Page';
}
}
이 코드를 보면서 가장 먼저 이해한 점은 @Controller() 데코레이터가 이 클래스가 라우팅을 담당한다는 것을 알려준다는 사실이었다.
메서드 위에 적힌 @Get() 데코레이터는 Express의 app.get()과 같은 역할을 한다. 특히 getHome() 메서드 위에는 @Get() // '/' 라고 주석이 적혀 있는데, 이는 별도의 경로 없이 루트 경로(/)를 처리한다는 의미로 이해했다. 반면 getPost()나 getUser() 메서드 위에도 @Get()만 작성되어 있는데, 이 부분은 실제 서비스라면 @Get('/post'), @Get('/User')처럼 경로를 명확히 지정해야 올바르게 동작할 것이라는 점을 기록으로 남겨둔다.
헷갈릴 수 있는 부분
Express 코드를 작성할 때 /post 경로 안에 <h1>Home Page</h1>를 넣었던 부분이나, NestJS 코드에서 @Get() 데코레이터에 구체적인 경로를 적지 않은 부분 등은 학습 과정에서 실험적으로 남겨둔 단서들이다. 이를 보면 경로(path)와 응답 데이터가 서로 매칭되어야 한다는 점을 나중에 다시 확인하기 위해 의도적으로 적어둔 것으로 보인다.
정리
정리하면, 이번 학습에서는 서버를 생성하고 요청에 응답하는 과정을 두 가지 방식으로 나누어 이해했다. Express에서는 직접 app 객체에 함수를 연결했다면, NestJS에서는 @Controller와 @Get 같은 데코레이터를 이용해 클래스 단위로 깔끔하게 라우팅을 관리한다는 사실을 코드를 통해 확실히 확인할 수 있었다.