오늘 한 일
오늘은 동아리 신청 CRUD를 끝내고 3일간 작업했던 Nest + socket.io(room) + 프론트 부분을 완성했다. 아직 조금 손 볼 곳이 남아 있긴 하지만, 매일같이 아침 9시 ~ 새벽 3시까지 스트레스 받으면서 쉬지도 않고 붙잡고 있던 터라 완성에 대한 기쁨이 너무 컸다. 사실 Nest + socket.io + room생성(여러 개의 룸에 여러 명의 참가자가 중복 가능)을 만들면서 참고할 부분이 없어서 매일같이 정리하면서 쓰고 싶었는데 진짜 정리할 시간이 없어서(진짜 논시간 없었음..) 내일이나 주말쯤 쭈욱 한 번 정리하고 싶다. 특히나 엔드포인트와 관련해서 생각한 부분이 있었는데 그 부분도 집중적으로 적어보고 싶다.
배운 부분
경로 설정 문제
Frontend와 Backend를 구분하는 API 경로를 만드는게 좋다.
예를 들면 Backend에서는 경로앞에 "/api"를 붙여서 구분을 해준다던가 Frontend에서는 경로앞에 "/view"를 붙여서 구분 해준다던가 하는 것이다.
이를 쉽게 하기 위해서 global prefix를 통해 백엔드 코드 따로, 프론트엔드 코드 따로 경로를 설정할 수 있다.
기본적으로 백엔드의 코드는 src 폴더 내에 생성하기 때문에 src의 영향을 받는 모든 폴더의 경로들은 globalprefix의 영향을 받는다.
아래의 경우 "api"로 지정해놓았기 때문에 src내에 존재하는 모든 폴더들은 첫 시작 경로가 "api"가 된다.
(localhost:<포트번호>/api 로 시작한다)
이 때, 나는 src 폴더 내에 프론트엔드 코드도 작성하고 있었기 때문에 prefix를 제외 해줘야 했는데 exclude를 써서 원하는 것을 제외해 줄 수 있다. 여기서 주의할 점은 exclude 안에 들어가는 내용이 폴더의 경로가 아닌 엔드포인트를 적어줘야 한다는 것이다. (아래의 예시에서는 포트번호 8001번 일때, localhost:8001/view/chat의 경로를 가지는 것에는 /api 경로가 붙지 않는다는 것이다 - localhost:8001/api/view/chat 와 같은 경로가 안된다는 것)
// main.ts
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useWebSocketAdapter(new SocketIoAdapter(app));
app.useStaticAssets(join(__dirname, "..", "public")); //html,js,css (바닐라)
app.setBaseViewsDir(join(__dirname, "..", "views"));
app.setViewEngine("ejs");
app.setGlobalPrefix("api", { exclude: ["/view/chat"] });
app.enableCors({
origin: true,
credentials: true,
});
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
whitelist: true,
// forbidNonWhitelisted: true,
}),
);
// ...
await app.listen(port);
}
bootstrap();
만약에 혹시나 경로 설정을 잘못해줘서 경로가 겹치게 된다면 시작 부분부터 가까운 코드가 우선적으로 화면에 로드되게 된다.
나의 경우 실수로 인해 Backend의 코드 경로와 Frontend의 코드 경로가 겹쳐버리는 일이 있었다. 이 때 문제를 해결해주면서 어떤 경로가 우선적으로 실행되는지 알게 되었다.
예를들어, localhost:3000이라는 경로를 Backend, Frontend 가 같이 사용하고 있다고 했을 때, 순서가 정해진 것이 아니라, 경로가 가까운 순서대로 화면에 띄워지는 것을 볼 수 있다.
이것은 아래의 사진을 보며 이해하면 좋을 것 같다.
(app.controller, app.service가 보통 가까워서 먼저 불리긴 한다.)
main.ts 시작 => app.module.ts
// app.module.ts
@Module({
imports: [
ConfigProjectModule,
TypeormModule.forRoot(),
AuthModule,
UserModule,
ClubModule,
ApplyingClubModule,
UserProfileModule,
UserCalenderModule,
RecruitModule,
MatchModule,
ChatBackEndModule,
ChatFrontEndModule,
],
controllers: [AppController, RecruitController],
providers: [AppService],
})
export class AppModule {}
app.module.ts 파일을 보면 위와 같이 작성되어 있는데, 진행되는 순서를 따지자면 controller - import 순으로 연결이 된다.
따라서 AppController(=>AppService) => RecruitController(=>RecruitService)가 실행되고 그 후 ConfigProjectModule => TypeormModule 순으로 내려가게 된다.
나의 경우 동일한 기본 경로가 AppController(Backend)와 ChatFrontEndController(Frontend)에 있었는데 둘 다 localhost:8001의 경로를 가지고 있었다. 위에서 설명한 것과 같이 AppController가 ChatFrontEndController보다 먼저 있었고 AppService에 있는 함수가 먼저 실행되어 브라우저 상에서 localhost:8001으로 들어가게 되면 AppService의 함수 결과가 떴었다.
렌더링 / 템플릿 엔진
// chatFrontend.controller.ts
import { Controller, Get, Render } from "@nestjs/common";
@Controller("view/chat")
export class ChatFrontEndController {
@Get()
@Render("index") //index.ejs
root() {
return { name: "윤찬" };
}
}
@Render - NestJS에서 사용되는 데코레이터. 템플릿을 렌더링하여 클라이언트에게 HTML 페이지를 반환하는 역할을 한다. 이 데코레이터는 특히 서버측 렌더링(SSR)을 지원하는 프레임워크에서 주로 사용된다.
여기서 "index"는 렌더링 할 템플릿 파일의 이름이며, 코드에서 주로 index.ejs나 index.hbs등과 같은 확장자를 생략하고 사용한다. (나의 경우는 index.ejs 사용, main.ts에서 app.setViewEngine("ejs")를 통해 사용) 템플릿 파일은 서버 측에서 데이터를 채워서 클라이언트에게 보내기 전에 사전에 정의된 형식에 따라 HTML로 변환된다.
예를 들면, <%= name %>를 ejs에 넣게 되었을 때, 이 name이라는 부분에 내가 지정한 "윤찬"이라는 데이터가 렌더링 된다.
root 메서드가 호출되면 {name:"윤찬"}이라는 데이터를 사용하여 index 템플릿을 렌더링하고, 그 결과를 클라이언트에게 반환한다. 클라이언트는 최종적으로 서버에서 렌더링된 HTML을 받게된다. 즉 서버측에서 데이터와 렌더링을 조합하는 서버측 렌더링의 예라고 할 수 있다.
'TIL' 카테고리의 다른 글
2024_01_11 TIL (2) | 2024.01.11 |
---|---|
2024_01_10 TIL (+01/09) (1) | 2024.01.11 |
2024_01_08 TIL (1) | 2024.01.09 |
2024_01_05 TIL (1) | 2024.01.06 |
2024_01_03 TIL (1) | 2024.01.04 |