10분 만에 코드 첫 줄부터 라이브 배포까지: 초고속 Nest.js 블로그 강좌
Daniel Hayes
Full-Stack Engineer · Leapcell

이것은 Nest.js 속성 강좌입니다. 이 튜토리얼에서는 몇 가지 간단한 단계만으로 코드의 첫 줄부터 배포까지 블로그를 구축할 것이며, 10분도 채 걸리지 않습니다.
이처럼 빠른 이유는 이 튜토리얼이 과정에 대한 자세한 설명을 파고들지 않기 때문입니다. 대신 완성된 제품을 구축하도록 직접 안내할 것입니다. 프레임워크를 배우려면 기존 프로젝트를 자신의 요구에 맞게 수정하는 것이 더 빠르다고 생각합니다.
이 블로그는 순수 Node.js 백엔드 프로젝트에 대한 일반적인 기술 스택을 나타내는 3가지 기능 모듈로 구성됩니다.
- Nest.js
- 데이터베이스로서의 PostgreSQL
- 페이지 렌더링을 위한 ejs
더 이상 지체하지 말고 시작하겠습니다:
1. 프로젝트 초기화
Nest.js 프로젝트는 프로젝트 관리를 위해 CLI 도구에 크게 의존합니다. 먼저 Nest.js CLI를 설치해 보겠습니다.
npm i -g @nestjs/cli
CLI를 사용하여 새 프로젝트를 만듭니다.
nest new personal-blog
그러면 personal-blog
라는 새 폴더가 생성되고 필요한 모든 종속성이 설치됩니다. 편집기에서 이 디렉터리를 열어 공식적으로 편집을 시작하세요.
2. PostgreSQL 데이터베이스 연결
다음으로 PostgreSQL 데이터베이스를 통합합니다. 공식 권장 사항에 따라 ORM으로 TypeORM을 사용합니다. ORM의 역할은 데이터베이스를 코드에 통합하는 것입니다.
종속성 설치
npm install @nestjs/typeorm typeorm pg
@nestjs/typeorm
: TypeORM을 Nest.js에 적용하는 TypeORM용 공식 Nest.js 모듈입니다.typeorm
: TypeORM 라이브러리 자체입니다.pg
: Node.js가 PostgreSQL을 읽고 쓸 수 있도록 하는 PostgreSQL용 Node.js 드라이버입니다.
데이터베이스 설정
튜토리얼 속도를 높이기 위해 데이터베이스를 로컬에 설치하고 설정하는 단계는 건너뛰겠습니다. 대신 온라인 데이터베이스를 직접 프로비저닝하겠습니다.
Leapcell에서 클릭 한 번으로 무료 데이터베이스를 만들 수 있습니다.
웹사이트에서 계정을 등록한 후 "데이터베이스 생성"을 클릭합니다.
데이터베이스 이름을 입력하고 배포 지역을 선택하면 PostgreSQL 데이터베이스를 만들 수 있습니다.
나타나는 새 페이지에서 데이터베이스에 연결하는 데 필요한 정보를 찾을 수 있습니다. 하단에 제공된 제어판을 통해 웹 페이지에서 데이터베이스를 직접 읽고 수정할 수 있습니다.
데이터베이스 연결 구성
src/app.module.ts
파일을 열고 TypeOrmModule
을 가져옵니다.
Leapcell의 데이터베이스 구성을 사용하여 연결 정보를 채웁니다. ssl
를 true
로 설정해야 합니다. 그렇지 않으면 연결이 실패합니다.
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'postgres', host: 'your_leapcell_host', // Replace with your Leapcell host port: 5432, username: 'your_postgres_username', // Replace with your PostgreSQL username password: 'your_postgres_password', // Replace with your PostgreSQL password database: 'personal_blog_db', // Replace with your database name entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, // Set to true in development, it automatically syncs the database schema ssl: true, // Required for services like Leapcell }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
게시물 모듈 생성
다음으로 블로그 게시물을 관리할 모듈을 만듭니다.
Nest CLI를 사용하여 필요한 파일을 빠르게 생성할 수 있습니다.
nest generate module posts nest generate controller posts nest generate service posts
그 후 데이터베이스와 연결하기 위해 Post
엔터티 파일을 만들어야 합니다. src/posts
디렉터리에서 post.entity.ts
라는 파일을 만듭니다.
// src/posts/post.entity.ts import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; @Entity() export class Post { @PrimaryGeneratedColumn('uuid') id: string; @Column() title: string; @Column('text') content: string; @CreateDateColumn() createdAt: Date; }
다음으로 데이터베이스에 연결해야 합니다.
PostsModule
에서 TypeOrmModule
을 등록합니다. src/posts/posts.module.ts
를 열고 TypeOrmModule.forFeature([Post])
를 가져옵니다.
// src/posts/posts.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; import { Post } from './post.entity'; @Module({ imports: [TypeOrmModule.forFeature([Post])], controllers: [PostsController], providers: [PostsService], }) export class PostsModule {}
Leapcell의 데이터베이스 세부 정보 페이지로 이동하여 웹 편집기에서 다음 명령을 실행하여 해당 테이블을 생성합니다.
CREATE TABLE "post" ( "id" UUID PRIMARY KEY DEFAULT gen_random_uuid(), "title" VARCHAR NOT NULL, "content" TEXT NOT NULL, "createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW() );
마지막으로 PostsService
를 만들어야 합니다. PostsService
는 게시물과 관련된 모든 비즈니스 로직을 처리합니다. src/posts/posts.service.ts
를 열고 다음 코드를 추가합니다.
// src/posts/posts.service.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Post } from './post.entity'; @Injectable() export class PostsService { constructor( @InjectRepository(Post) private postsRepository: Repository<Post> ) {} findAll(): Promise<Post[]> { return this.postsRepository.find({ order: { createdAt: 'DESC', }, }); } findOne(id: string): Promise<Post> { return this.postsRepository.findOneBy({ id }); } async create(post: Partial<Post>): Promise<Post> { const newPost = this.postsRepository.create(post); return this.postsRepository.save(newPost); } }
페이지 렌더링을 위한 EJS 설정
이제 동적 HTML 페이지를 렌더링할 수 있도록 EJS를 설정합니다.
종속성 설치
npm install ejs
뷰 엔진 통합
src/main.ts
파일을 열고 다음과 같이 변경합니다.
// src/main.ts import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { join } from 'path'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create<NestExpressApplication>(AppModule); app.useStaticAssets(join(__dirname, '..', 'public')); app.setBaseViewsDir(join(__dirname, '..', 'views')); app.setViewEngine('ejs'); await app.listen(3000); } bootstrap();
프로젝트 루트 디렉터리 (src
와 같은 수준)에 views
및 public
폴더를 만듭니다.
- personal-blog
- src
- views
- public
프런트엔드 페이지 구현
views
폴더에 다음 파일을 만듭니다.
-
_header.ejs
(재사용 가능한 헤더)<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title><%= title %></title> <link rel="stylesheet" href="/css/style.css" /> </head> <body> <header> <h1><a href="/">My Blog</a></h1> <a href="/posts/new" class="new-post-btn">New Post</a> </header> <main></main> </body> </html>
-
_footer.ejs
(재사용 가능한 푸터)</main> <footer> <p>© 2025 My Blog</p> </footer> </body> </html>
-
index.ejs
(블로그 홈페이지)<%- include('_header', { title: 'Home' }) %> <div class="post-list"> <% posts.forEach(post => { %> <article class="post-item"> <h2><a href="/posts/<%= post.id %>"><%= post.title %></a></h2> <p><%= post.content.substring(0, 150) %>...</p> <small><%= new Date(post.createdAt).toLocaleDateString() %></small> </article> <% }) %> </div> <%- include('_footer') %>
-
post.ejs
(개별 게시물 페이지)<%- include('_header', { title: post.title }) %> <article class="post-detail"> <h1><%= post.title %></h1> <small><%= new Date(post.createdAt).toLocaleDateString() %></small> <div class="post-content"><%- post.content.replace(/\n/g, '<br />') %></div> </article> <a href="/" class="back-link">← Back to Home</a> <%- include('_footer') %>
-
new-post.ejs
(새 게시물 페이지)<%- include('_header', { title: 'New Post' }) %> <form action="/posts" method="POST" class="post-form"> <div class="form-group"> <label for="title">Title</label> <input type="text" id="title" name="title" required /> </div> <div class="form-group"> <label for="content">Content</label> <textarea id="content" name="content" rows="10" required></textarea> </div> <button type="submit">Submit</button> </form> <%- include('_footer') %>
2. CSS 스타일 추가
public/css/style.css
에 간단한 스타일을 추가합니다.
/* public/css/style.css */ body { font-family: sans-serif; line-height: 1.6; margin: 0; background-color: #f4f4f4; color: #333; } header { background: #333; color: #fff; padding: 1rem; display: flex; justify-content: space-between; align-items: center; } header a { color: #fff; text-decoration: none; } main { max-width: 800px; margin: 2rem auto; padding: 1rem; background: #fff; border-radius: 5px; } .post-item { margin-bottom: 2rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; } .post-item h2 a { text-decoration: none; color: #333; } .post-detail .post-content { margin-top: 1rem; } .new-post-btn { background: #5cb85c; padding: 0.5rem 1rem; border-radius: 5px; } .post-form .form-group { margin-bottom: 1rem; } .post-form label { display: block; margin-bottom: 0.5rem; } .post-form input, .post-form textarea { width: 100%; padding: 0.5rem; } .post-form button { background: #337ab7; color: #fff; padding: 0.7rem 1.5rem; border: none; cursor: pointer; } footer p { text-align: center; }
백엔드를 프런트엔드 페이지와 연결
src/posts/posts.controller.ts
를 업데이트하여 HTTP 요청을 처리하고 EJS 템플릿을 렌더링합니다.
// src/posts/posts.controller.ts import { Controller, Get, Render, Param, Post, Body, Res } from '@nestjs/common'; import { PostsService } from './posts.service'; import { Response } from 'express'; @Controller('posts') export class PostsController { constructor(private readonly postsService: PostsService) {} @Get() @Render('index') async root() { const posts = await this.postsService.findAll(); return { posts }; } @Get('new') @Render('new-post') newPostForm() { return; } @Post() async create(@Body() body: { title: string; content: string }, @Res() res: Response) { await this.postsService.create(body); res.redirect('/posts'); } @Get(':id') @Render('post') async post(@Param('id') id: string) { const post = await this.postsService.findOne(id); return { post }; } }
다음으로 루트 경로 /
를 블로그 홈페이지로 리디렉션하도록 src/app.controller.ts
를 업데이트합니다.
// src/app.controller.ts import { Controller, Get, Res } from '@nestjs/common'; import { Response } from 'express'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() root(@Res() res: Response) { return res.redirect('/posts'); } }
블로그 실행
블로그를 시작하려면 터미널에서 다음 명령을 실행합니다.
npm run start:dev
브라우저에서 http://localhost:3000
을 열어 블로그 페이지를 확인하세요. 새 게시물을 작성해 보세요!
이제 웹사이트와 API를 원하는 대로 수정하여 그 과정에서 Nest.js에 대한 이해를 심화시킬 수 있습니다.
블로그 온라인 배포
이제 직접 만든 웹사이트를 다른 사람들에게 보여주어 누구나 액세스할 수 있도록 하는 방법을 생각하고 있을지도 모릅니다.
이전에 데이터베이스를 만드는 데 사용했던 Leapcell을 기억하시나요? Leapcell은 데이터베이스 생성 이상의 기능을 제공합니다. Nest.js를 포함한 다양한 언어와 프레임워크의 프로젝트를 호스팅할 수 있는 웹 앱 호스팅 플랫폼이기도 합니다.
아래 단계를 따르세요.
- 프로젝트를 GitHub에 커밋합니다. 단계는 GitHub 공식 문서를 참조할 수 있습니다. Leapcell은 나중에 GitHub 저장소에서 코드를 가져올 것입니다.
- Leapcell 페이지에서 "서비스 생성"을 클릭합니다.
- Nest.js 저장소를 선택하면 Leapcell이 필요한 구성을 자동으로 채워줍니다.
- 하단의 "제출"을 클릭하여 배포합니다. 배포는 빠르게 완료되고 배포 홈페이지로 돌아갑니다. 여기서 Leapcell이 도메인을 제공했음을 볼 수 있습니다. 이것이 블로그의 온라인 주소입니다.
이제 이 링크를 친구들과 공유하면 모든 사람이 온라인에서 자신의 블로그를 볼 수 있습니다!