10 Minutes from First Line of Code to Live Deployment: A Super Fast Nest.js Blog Course



This content originally appeared on DEV Community and was authored by Leapcell

Cover

This is a super fast course on Nest.js. In this tutorial, we will build a blog from the first line of code to deployment in just a few simple steps, taking less than 10 minutes.

The reason it’s so fast is that this tutorial won’t delve into detailed explanations of the process. Instead, it will guide you directly to build a finished product. I believe that learning a framework is faster by modifying an existing project to fit your own needs.

This blog consists of 3 functional modules, representing a common technology stack for a pure Node.js backend project:

  • Nest.js
  • PostgreSQL, as the database
  • ejs, for rendering pages

Without further ado, let’s get started:

1. Initialize the Project

Nest.js projects rely heavily on the CLI tool for project management. First, let’s install the Nest.js CLI.

npm i -g @nestjs/cli

Use the CLI to create a new project.

nest new personal-blog

This will create a new folder named personal-blog and install all the necessary dependencies. Open this directory in your favorite editor to officially start editing.

2. Connect to the PostgreSQL Database

Next, we will integrate a PostgreSQL database. Following the official recommendation, we’ll use TypeORM as the ORM. The role of an ORM is to integrate the database into the code.

Install Dependencies

npm install @nestjs/typeorm typeorm pg
  • @nestjs/typeorm: The official Nest.js module for TypeORM, adapting TypeORM for Nest.js.
  • typeorm: The TypeORM library itself.
  • pg: The Node.js driver for PostgreSQL, enabling Node.js to read and write to PostgreSQL.

Set up the Database

To speed up the tutorial, we will skip the step of installing and setting up a database locally. Instead, we’ll provision an online database directly.

We can create a free database with one click on Leapcell.

Leapcell

After registering an account on the website, click “Create Database”.

ImageP1

Enter a Database name, select a deployment region, and you can create the PostgreSQL database.

On the new page that appears, you will find the information needed to connect to the database. A control panel is provided at the bottom, allowing you to read and modify the database directly on the webpage.

ImageP2

Configure the Database Connection

Open the src/app.module.ts file and import TypeOrmModule.

Fill in the connection information using the database configuration from Leapcell. Note that sslMode must be set to true, otherwise, the connection will fail.

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 {}

Create the Posts Module

Next, let’s create a module to manage blog posts.

You can use the Nest CLI to quickly generate the required files:

nest generate module posts
nest generate controller posts
nest generate service posts

After that, we need to create the Post entity file to connect it with the database. In the src/posts directory, create a file named 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;
}

Next, we need to connect to the database.

Register TypeOrmModule in PostsModule: open src/posts/posts.module.ts and import 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 {}

Go to the Database details page on Leapcell and execute the following command in the web editor to generate the corresponding table.

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()
);

Finally, we need to create the PostsService. The PostsService will be responsible for handling all business logic related to posts. Open src/posts/posts.service.ts and add the following code:

// 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);
  }
}

Set Up EJS for Page Rendering

Now, we’ll set up EJS so that we can render dynamic HTML pages.

Install Dependencies

npm install ejs

Integrate the View Engine

Open the src/main.ts file and change it to the following:

// 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();

In the project root directory (at the same level as src), create views and public folders:

- personal-blog
    - src
    - views
    - public

Implement the Frontend Pages

Create the following files in the views folder:

  • _header.ejs (Reusable header)
  <!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 (Reusable footer)
      </main>
      <footer>
          <p>&copy; 2025 My Blog</p>
      </footer>
  </body>
  </html>
  • index.ejs (Blog homepage)
  <%- 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 (Post detail page)
  <%- 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">&larr; Back to Home</a>

  <%- include('_footer') %>
  • new-post.ejs (New post page)
  <%- 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. Add CSS Styles

Add some simple styles in 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;
}

Connect the Backend with the Frontend Pages

Update src/posts/posts.controller.ts to handle HTTP requests and render EJS templates.

// 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 };
  }
}

Next, update src/app.controller.ts to redirect the root path / to the blog homepage.

// 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');
  }
}

Run the Blog

Run the following command in your terminal to start the blog:

npm run start:dev

Open http://localhost:3000 in your browser to see the blog page. You can try writing a new post!

ImageP3

ImageP4

Now you can modify the website and API as you like, deepening your understanding of Nest.js in the process.

Deploy the Blog Online

Now you might be thinking, how can I show the website I made to others so that everyone can access it?

Remember Leapcell, which we used to create the database earlier? Leapcell can do more than just create databases; it’s also a web app hosting platform that can host projects in various languages and frameworks, including Nest.js, of course.

Leapcell

Follow the steps below:

  1. Commit your project to GitHub. You can refer to GitHub’s official documentation for the steps. Leapcell will pull the code from your GitHub repository later.
  2. Click “Create Service” on the Leapcell page. ImageP5
  3. After choosing your Nest.js repo, you’ll see Leapcell has auto-populated the necessary configurations. ImageP6
  4. Click “Submit” at the bottom to deploy. The deployment will complete quickly and return you to the deployment homepage. Here we can see that Leapcell has provided a domain. This is the online address of your blog. ImageP7

Now, you can share this link with your friends, and everyone can see your blog online!

Follow us on X: @LeapcellHQ

Read on our blog

Related Posts:


This content originally appeared on DEV Community and was authored by Leapcell