NestJS + PostgreSQL: Create Your First REST API

CRUD example with TypeORM

Building a REST API with NestJS, PostgreSQL, and TypeORM is a powerful, scalable choice for production apps. In this guide, you’ll create a complete CRUD API (Create, Read, Update, Delete) using a simple Todo resource.

We’ll cover project setup, database configuration, an entity + DTOs, a service, a controller, and how to test with cURL.


Prerequisites

  • Node.js 18+
  • PostgreSQL 13+ running locally (with a database created)
  • npm or pnpm
  • Nest CLI:
npm i -g @nestjs/cli

1) Create the Project

nest new nest-pg-crud
# Choose npm or pnpm
cd nest-pg-crud

Install required packages:

npm i @nestjs/typeorm typeorm pg class-validator class-transformer dotenv

2) Environment Variables

Create a .env file in the project root:

# .env
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=postgres
DB_NAME=nest_crud

Ensure the database nest_crud exists:

CREATE DATABASE nest_crud;

3) TypeORM + PostgreSQL Setup

Open src/app.module.ts and configure TypeORM (v0.3+ style):

// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TodoModule } from './todo/todo.module';
import { Todo } from './todo/todo.entity';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      useFactory: () => ({
        type: 'postgres',
        host: process.env.DB_HOST,
        port: Number(process.env.DB_PORT),
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME,
        entities: [Todo],
        synchronize: true, // turn off in production; use migrations instead
        logging: false,
      }),
    }),
    TodoModule,
  ],
})
export class AppModule {}

4) Create the Todo Module

Generate files:

nest g module todo
nest g service todo
nest g controller todo

5) Define the Entity

// src/todo/todo.entity.ts
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Todo {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ length: 120 })
  title: string;

  @Column({ type: 'text', nullable: true })
  description?: string;

  @Column({ default: false })
  completed: boolean;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;
}

Register the entity in the module:

// src/todo/todo.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Todo } from './todo.entity';
import { TodoService } from './todo.service';
import { TodoController } from './todo.controller';

@Module({
  imports: [TypeOrmModule.forFeature([Todo])],
  controllers: [TodoController],
  providers: [TodoService],
})
export class TodoModule {}

6) Create DTOs (Validation)

// src/todo/dto/create-todo.dto.ts
import { IsBoolean, IsOptional, IsString, MaxLength } from 'class-validator';

export class CreateTodoDto {
  @IsString()
  @MaxLength(120)
  title: string;

  @IsOptional()
  @IsString()
  description?: string;

  @IsOptional()
  @IsBoolean()
  completed?: boolean;
}
// src/todo/dto/update-todo.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateTodoDto } from './create-todo.dto';

export class UpdateTodoDto extends PartialType(CreateTodoDto) {}

Enable global validation pipe in main.ts:

// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
  await app.listen(process.env.PORT || 3000);
}
bootstrap();

Install @nestjs/mapped-types (for PartialType) if not present:

npm i @nestjs/mapped-types

7) Service (Business Logic)

// src/todo/todo.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { Todo } from './todo.entity';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';

@Injectable()
export class TodoService {
  constructor(
    @InjectRepository(Todo)
    private readonly repo: Repository<Todo>,
  ) {}

  async create(dto: CreateTodoDto): Promise<Todo> {
    const todo = this.repo.create(dto);
    return this.repo.save(todo);
  }

  async findAll(q?: string, page = 1, limit = 10): Promise<{ data: Todo[]; total: number; page: number; limit: number }> {
    const where: FindOptionsWhere<Todo>[] = q
      ? [{ title: ILike(`%${q}%`) }, { description: ILike(`%${q}%`) }]
      : [{}];

    const [data, total] = await this.repo.findAndCount({
      where,
      order: { createdAt: 'DESC' },
      skip: (page - 1) * limit,
      take: limit,
    });

    return { data, total, page, limit };
  }

  async findOne(id: string): Promise<Todo> {
    const todo = await this.repo.findOne({ where: { id } });
    if (!todo) throw new NotFoundException('Todo not found');
    return todo;
  }

  async update(id: string, dto: UpdateTodoDto): Promise<Todo> {
    const todo = await this.findOne(id);
    Object.assign(todo, dto);
    return this.repo.save(todo);
  }

  async remove(id: string): Promise<void> {
    const todo = await this.findOne(id);
    await this.repo.remove(todo);
  }
}

8) Controller (Routes)

// src/todo/todo.controller.ts
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { TodoService } from './todo.service';
import { CreateTodoDto } from './dto/create-todo.dto';
import { UpdateTodoDto } from './dto/update-todo.dto';

@Controller('todos')
export class TodoController {
  constructor(private readonly service: TodoService) {}

  @Post()
  create(@Body() dto: CreateTodoDto) {
    return this.service.create(dto);
  }

  @Get()
  findAll(
    @Query('q') q?: string,
    @Query('page') page = '1',
    @Query('limit') limit = '10',
  ) {
    return this.service.findAll(q, Number(page), Number(limit));
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.service.findOne(id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() dto: UpdateTodoDto) {
    return this.service.update(id, dto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.service.remove(id);
  }
}

9) Start the Server

npm run start:dev
# API runs at http://localhost:3000

10) Test the Endpoints (cURL)

Create

curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn NestJS","description":"Build a CRUD API","completed":false}'

List (with pagination + search)

curl "http://localhost:3000/todos?page=1&limit=5&q=nest"

Get by ID

curl http://localhost:3000/todos/<UUID>

Update

curl -X PATCH http://localhost:3000/todos/<UUID> \
  -H "Content-Type: application/json" \
  -d '{"completed": true}'

Delete

curl -X DELETE http://localhost:3000/todos/<UUID>

Production Tips

  • Turn off synchronize and use migrations for schema changes.
  • Add a global exception filter and logging middleware.
  • Secure env vars with a proper config and secrets management.
  • Dockerize Postgres + API for consistent environments.

Wrap-Up

You now have a fully working NestJS + PostgreSQL REST API with TypeORM, DTO validation, pagination, and search. This structure scales well for real projects—just add modules for new domains and keep business logic inside services.


Optional: Package Scripts

// package.json (partial)
{
  "scripts": {
    "start": "nest start",
    "start:dev": "nest start --watch",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\"",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
  }
}

Comments

21 responses to “NestJS + PostgreSQL: Create Your First REST API”

  1. Honestly, the depth of this analysis is helpful. Trying to grasp all the mechanisms described, especially for beginners, is a lot. I might need to check out 7jl login app download apk later to see how simple the actual process is. 🤔

  2. This is a solid foundation for scalable backend architecture. Moving beyond basic CRUD, consider how transaction management and eventual consistency impact real-world user flows. Building robust, high-availability systems-whether for a corporate service or a high-traffic platform like jil29 link-requires this level of structural rigor. Excellent guide!

  3. Perfect for those who want a straightforward gaming experience without too much fuss. Very intuitive layout over at win29au

  4. Such a fun way to unwind after work. The games are exciting and the registration was super quick. I really enjoy juegolota

  5. I’ve been looking for a reliable app like this for a while. The interface is super smooth and the payouts are actually fast. Highly recommended for anyone in the region! Check out 4999bdtapp

  6. That’s a fascinating take on longshot strategies! It’s smart to consider value beyond just the favorites. Thinking about quick access & security, like with 36jl casino’s fast registration, really helps focus on the bets themselves! 👍

  7. Finally found a platform that truly connects! The KingphaPK app download apk is smooth and the community vibe is amazing. Check out kingphapk vip to join the fun today!

  8. I switched from my usual spot and honestly I do not plan on going back. The chat support is always friendly and the withdrawal process is surprisingly fast. It just feels like a community that genuinely enjoys gaming. 19jl

  9. I spent weeks testing different platforms before finding one that actually delivers on fair gameplay. The slot selection is massive and the payout schedule never disappoints. I appreciate how transparent everything is from bonus terms to withdrawal limits. It is refreshing to find a team that truly cares about their players. iddanatotoslot

  10. Logging in here feels like stepping into a VIP lounge where everything just works. I appreciate how quickly they verify new accounts and how transparent the bonus terms really are. No hidden traps just honest gameplay and fair chances to win big. The support crew responds in record time whenever I need a hand. Visit roolicasinologin and experience the difference yourself

  11. Finally a site that truly gets the Latin American gaming scene and brings a VIP treatment to everyone who signs up. The games load perfectly on mobile and I never have to worry about hidden conditions. It is rare to find this level of respect for everyday players. vipganer

  12. This trading platform has completely changed how I approach the forex market. The charts are clean execution is lightning fast and the educational resources helped me improve my strategy significantly. A solid choice for both new and experienced traders. 888fx

  13. This casino quietly built a solid reputation among locals and I can totally see why. The welcome package is fair, game providers are top tier and withdrawals never get stuck in review for days. A trustworthy friend for anyone chasing lucky streaks. Come join 578jilicasino

  14. The onboarding process seems straightforward, but experienced players know the real edge is in understanding the mechanics, not just the sign-up. Check out P7777 vip for deeper insights.

  15. The focus on mobile convenience is noted, but remember that true platform value lies in verifiable transaction integrity. For deeper analysis, check out 234win 444 download apk. Statistical rigor always precedes entertainment.

  16. Everything from the layout to the game selection is top-notch. I feel very secure playing here and the rewards are generous. Go check pknb999 right now.

  17. Really impressed with the variety of betting options available. It is easy to navigate even for beginners. I’m sticking with 2nnbet for my weekend games.

  18. This interface feels clean, which is rare. While the focus on simple fun is noted, checking out big bunny com‘s overall usability against competitors would be smart for any serious player.

  19. This platform truly stands out with its smooth interface and top notch game selection. The support team is super helpful and I trust them for every session at alpha66online

  20. Even my relaxation time has a fun twist with this platform. The games are so smoothly integrated that playing feels as comfortable as settling into my favorite couch. A truly cozy escape for any downtime. masterbed

  21. Being a player in the Philippines I know how hard it is to find a site that actually pays out on time. This one changed my routine completely. I love the live dealer tables and the payment options work perfectly with local banks. The interface is clean and I never feel lost while placing my bets. Highly recommend it to everyone looking for a trustworthy platform. nn7777ph

Leave a Reply

Your email address will not be published. Required fields are marked *

汽水音乐mumu模拟器谷歌邮箱telegramtodesk易翻译