---
title: "How to Create Your First REST API with NestJS and Node.js"
date: "2023-09-19"
slug: "how-to-create-your-first-api-with-nest-effortlessly"
author: "Dany Paredes"
canonical: "https://danywalls.com/how-to-create-your-first-api-with-nest-effortlessly"
description: "Build a modular REST API with Node.js and NestJS, handle GET and POST requests, enable CORS, and consume endpoints from Angular."
---


When building prototypes or sample applications, frontend developers often need real server data. Mock servers like `json-server` or free mock APIs work for trivial demos. However, they quickly fall short when you need custom business logic, file processing, or custom POST endpoints.

How can a frontend developer build a robust, production-ready backend in Node.js without wrestling with unstructured Express code?

My favorite solution is **[NestJS](https://nestjs.com/)**. Because NestJS uses TypeScript, modules, controllers, and dependency injection, the mental model is clean, structured, and familiar.

In this guide, I will show you how to build a NestJS REST API from scratch, handle dynamic route parameters, process POST payloads, configure CORS, and consume the API in modern Angular.

---

## 1. Why NestJS for Node.js Applications?

Node.js gives you freedom, but in plain Express projects, that freedom often leads to messy code without architectural consistency.

NestJS solves this problem by providing a modular architecture out of the box:
- **Controllers**: Handle incoming HTTP requests and return responses.
- **Providers / Services**: Encapsulate business logic, database queries, and third-party integrations.
- **Modules**: Group related controllers and services into clean domain boundaries.
- **Pipes & Guards**: Validate incoming request bodies and protect private endpoints.

Let us start by generating our project.

---

## 2. Generating the NestJS Project

First, make sure you have Node.js (v18 or higher) installed. Then install the official NestJS CLI:

```bash
npm install -g @nestjs/cli
```

> Tip: You can also use `npx @nestjs/cli new jumbo_api` if you do not want to install global packages on your machine.

Now generate a new project named `jumbo_api`:

```bash
nest new jumbo_api
```

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/c933c04d-ddd2-4_93228.png)

Once the generator finishes, navigate into the project folder and start the development server:

```bash
cd jumbo_api
npm run start:dev
```

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/e693da73-7cc8-4_93229.png)

Open `http://localhost:3000` in your browser. You will see the default `"Hello World!"` response.

The `start:dev` command runs the application in watch mode, automatically recompiling whenever you save changes.

---

## 3. Understanding the Core Project Structure

Open the generated project in your editor. You will see four primary files inside `src/`:

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/d619bc41-13fe-4_93229.png)

1. **`main.ts`**: The entry point that boots the application using `NestFactory.create()`:

```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
```

2. **`app.module.ts`**: The root module where controllers and providers are registered:

```typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

3. **`app.controller.ts`**: Maps HTTP routes to methods:

```typescript
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
```

Now let us create our own custom feature controller.

---

## 4. Creating a Feature Controller with GET Endpoints

We will create a `cards` controller to manage membership cards. Run the Nest CLI generator:

```bash
nest generate controller cards
```

This command creates `src/cards/cards.controller.ts` and automatically imports it into `AppModule`.

### Returning a List of Objects

Open `src/cards/cards.controller.ts` and add a `getAll` method:

```typescript
import { Controller, Get } from '@nestjs/common';

export interface Card {
  id: number;
  code: string;
  validDate: string;
}

@Controller('cards')
export class CardsController {
  @Get('list')
  getAll(): Card[] {
    return [
      {
        id: 1,
        code: 'CARD-1001',
        validDate: new Date().toLocaleDateString('en-US'),
      },
      {
        id: 2,
        code: 'CARD-1002',
        validDate: new Date().toLocaleDateString('en-US'),
      },
    ];
  }
}
```

Because the controller has `@Controller('cards')` and the method has `@Get('list')`, navigating to `http://localhost:3000/cards/list` returns the JSON array:

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/f9459ecd-0f3c-4_93229.png)

Nest automatically serializes JavaScript arrays and objects into JSON and sets `Content-Type: application/json`.

---

## 5. Reading Dynamic Route Parameters

What if we want an endpoint like `/cards/generate/:name` that reads a parameter from the URL path?

We use the `@Param('name')` decorator:

```typescript
import { Controller, Get, Param } from '@nestjs/common';

@Controller('cards')
export class CardsController {
  @Get('generate/:name')
  generateCard(@Param('name') name: string) {
    return {
      id: Math.floor(Math.random() * 1000),
      code: `CARD-${Math.random().toString(36).substring(2, 7).toUpperCase()}`,
      validDate: new Date().toLocaleDateString('en-US'),
      holder: name,
    };
  }
}
```

When you open `http://localhost:3000/cards/generate/lebronjames`, Nest extracts `lebronjames` from the URL and passes it directly to your method:

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/44705db8-fca0-4_93229.png)

Now let us handle incoming POST payloads.

---

## 6. Handling POST Requests and Request Bodies

To accept incoming JSON payloads in a POST request, use `@Post()` and `@Body()`:

```typescript
import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';

export interface SubscribeDto {
  name: string;
}

@Controller('cards')
export class CardsController {
  @Post('subscribe')
  @HttpCode(HttpStatus.OK)
  subscribe(@Body() body: SubscribeDto) {
    return {
      id: Math.floor(Math.random() * 10000),
      name: body.name,
      active: true,
      createdAt: new Date().toISOString(),
    };
  }
}
```

Notice the `@HttpCode(HttpStatus.OK)` decorator. By default, Nest returns HTTP status `201 Created` for POST requests. Adding `@HttpCode(HttpStatus.OK)` changes the status code to `200 OK` when appropriate.

Send a POST request with Postman or curl:

```bash
curl -X POST http://localhost:3000/cards/subscribe \
  -H "Content-Type: application/json" \
  -d '{"name": "Dany Walls"}'
```

The server responds with our structured JSON object:

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/c7906356-4b78-4_93229.png)

Before we connect our frontend, there is one critical step: enabling CORS.

---

## 7. Enabling CORS in NestJS

If your frontend runs on `http://localhost:4200` and your API runs on `http://localhost:3000`, the browser blocks requests due to the Same-Origin Policy.

In NestJS, enabling Cross-Origin Resource Sharing takes one line. Open `src/main.ts`:

```typescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Enable CORS for frontend clients
  app.enableCors();

  await app.listen(3000);
}
bootstrap();
```

You can also pass options to `app.enableCors({ origin: 'http://localhost:4200' })` to restrict access in production.

Now let us consume the API in Angular.

---

## 8. Consuming the API in Modern Angular

Create an Angular standalone application:

```bash
npx @angular/cli new jumbo_web --standalone --routing=false --style=scss
```

### Configuring HttpClient in `app.config.ts`

In modern Angular, configure `provideHttpClient()` in `src/app/app.config.ts`:

```typescript
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideHttpClient(),
  ],
};
```

### Creating the Card Service with Signals

Create a service to interact with the NestJS API:

```typescript
// src/app/cards.service.ts
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

export interface Card {
  id: number;
  code: string;
  validDate: string;
}

@Injectable({ providedIn: 'root' })
export class CardsService {
  #http = inject(HttpClient);
  #apiUrl = 'http://localhost:3000/cards';

  // Automatically converts Observable to a reactive Signal
  cards = toSignal(this.#http.get<Card[]>(`${this.#apiUrl}/list`), { initialValue: [] });
  activeCard = signal<Card | null>(null);

  generate(name: string) {
    this.#http.post<Card>(`${this.#apiUrl}/subscribe`, { name })
      .subscribe((card) => {
        this.activeCard.set(card);
      });
  }
}
```

### Displaying the Data with Modern Control Flow (`@if` and `@for`)

In `src/app/app.component.ts`:

```typescript
import { Component, inject } from '@angular/core';
import { CardsService } from './cards.service';

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
    <main class="container">
      <h1>Membership Cards</h1>

      <div class="form-group">
        <label for="name">Your Name:</label>
        <input id="name" #nameInput placeholder="Enter your name" />
        <button (click)="createCard(nameInput.value)">Generate Card</button>
      </div>

      @if (cardsService.activeCard(); as card) {
        <div class="alert-success">
          <p>Card generated for: <strong>{{ card.name }}</strong> (ID: {{ card.id }})</p>
        </div>
      }

      <h2>Available Cards</h2>
      <div class="cards-grid">
        @for (item of cardsService.cards(); track item.id) {
          <div class="card">
            <span>Code: {{ item.code }}</span>
            <span>Valid Until: {{ item.validDate }}</span>
          </div>
        } @empty {
          <p>No cards found.</p>
        }
      </div>
    </main>
  `,
})
export class AppComponent {
  cardsService = inject(CardsService);

  createCard(name: string) {
    if (name.trim()) {
      this.cardsService.generate(name);
    }
  }
}
```

> Note for Legacy Angular (v16 and earlier): You can use `*ngIf="cardsService.activeCard()"` and `*ngFor="let item of cardsService.cards()"` by importing `CommonModule`. Modern Angular (v17+) uses the `@if` and `@for` control flow syntax shown above.

Run `npm start` in your Angular project, open `http://localhost:4200`, and you have a connected full-stack application!

![](/images/posts/how-to-create-your-first-api-with-nest-effortlessly/433f313b-e9d7-4_93229.gif)

---

## 9. Containerizing the API with Docker

To deploy the NestJS API consistently across cloud environments, create a `Dockerfile` inside `jumbo_api/`:

```dockerfile
# Multi-stage production build
FROM node:22-alpine AS build

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# Production runtime stage
FROM node:22-alpine AS runner

WORKDIR /usr/src/app
ENV NODE_ENV=production

COPY package*.json ./
RUN npm ci --omit=dev

COPY --from=build /usr/src/app/dist ./dist

EXPOSE 3000
CMD ["node", "dist/main.js"]
```

Build and test your container image:

```bash
docker build -t jumbo-api:latest .
docker run -p 3000:3000 jumbo-api:latest
```

Your API is now packaged as an isolated, lightweight container ready for Google Cloud Run, AWS ECS, or any container runtime.

---

## Conclusion & Next Steps

Building Node.js APIs with NestJS gives you structure without sacrificing developer velocity:

1. **Leverage modularity**: Use controllers for HTTP routing and services for business logic.
2. **Use strict types**: Define DTOs (Data Transfer Objects) and interfaces to share contracts between frontend and backend.
3. **Turn on CORS early**: Avoid frustrating browser errors by configuring `app.enableCors()` during setup.

If you want to continue expanding your backend and cloud skills, check out these related guides:

- [Managing HTTP Errors in NestJS](https://danywalls.com/managing-http-errors-in-nestjs-effortlessly)
- [Moving from Vercel Next.js to Google Cloud Run](https://danywalls.com/migrating-nextjs-from-vercel-to-gcp)
- [How to Detect and Fix Circular Dependencies in TypeScript](https://danywalls.com/how-to-detect-and-fix-circular-dependencies-in-typescript)
- [How to Check Types in TypeScript](https://danywalls.com/how-to-check-types-in-typescript)

