DEV Community

Cover image for NestJs: 🐶 Puppies Api
Abayomi Ogunnusi
Abayomi Ogunnusi

Posted on • Updated on

NestJs: 🐶 Puppies Api

NestJS is a Node.js back-end development framework built upon Express, leveraging the power of TypeScript.

In this lesson, we will learn how to create a Rest Api using NestJs. Get the source code here.

To add swagger docs and deploy to heroku check here


🚀 Quick start:
Install node
Install nest cli: `npm i -g @nestjs/cli`
Initialize a nestjs project `nest new project-name`
Enter fullscreen mode Exit fullscreen mode
Technology used:

Nodejs, NestJs, Psql, TypeOrm, Git


Agenda

🐶 Register a puppy
🐶 Get a puppy
🐶 Get all puppies
🐶 Update puppy profile
🐶 Delete a puppy profile after adoption


For the love of puppies, let's get started

puppies


Let's start by creating our project. I will call it puppies.

Image description

On successful pull, you get:
Image description


Let's change into the directory to run the puppies app
Image description

Lets see the folder structure that came preinstalled with NestJs
Image description

To start the app, run yarn start:dev
Image description
The above command produces the dist folder, this is the compilation of our Type Script files into Vanilla JavaScript .

Now, let's see if our app is running. NestJs by default, runs on localhost:3000. To see that in action we use:
Image description


Now that we have got our app with no error, let's dive into each file.

Main.ts

Let's go into the main entry file. Our app runs on port:3000 like I said earlier. We can change the port to other than 3000. we will use port 7890 in this tutorial.
Image description

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

const port = process.env.PORT || 7890;
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    abortOnError: false,
  });
  await app.listen(port);
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

If you noticed i added the abortOnError: false, this will not make your app exit if any error happens instead it throws an error


Controllers

Controllers are responsible for handling incoming requests and returning responses to the client.

import { Controller, Delete, Get, Post, Put } from '@nestjs/common';
import { AppService } from './app.service';

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

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

  @Post()
  registerPuppy() {
    return this.appService.register();
  }

  @Get(':id')
  getPuppy(id: string) {
    return this.appService.read(id);
  }

  @Get()
  getPuppies() {
    return this.appService.readAll();
  }

  @Put(':id')
  updatePuppy(id: string, puppy: any) {
    return this.appService.update(id, puppy);
  }

  @Delete(':id')
  deletePuppy(id: string) {
    return this.appService.delete(id);
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's move to our Service to flesh out the register, read, readAll, update and delete logic.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }

  register() {
    return 'Puppy registered!';
  }

  read(id: string) {
    return `Puppy with id ${id}`;
  }

  readAll() {
    return 'All puppies';
  }

  update(id: string, puppy: any) {
    return `Puppy with id ${id} updated`;
  }

  delete(id: string) {
    return `Puppy with id ${id} deleted`;
  }
}
Enter fullscreen mode Exit fullscreen mode

Database and entities

Let's us design our database entities[schemas] should look like. We install the typeorm library that will help us connect to the db.
Image description

Before we go further, let us create our database using the terminal.
Image description

Install the pg, the non-blocking PostgreSQL client for Node.js.

Image description

Next, we create our ormconfig.js file where our database credentials lies

require('dotenv').config();

module.exports = {
  name: 'default',
  type: 'postgres',
  host: process.env.DATABASE_HOST,
  port: 5432,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE_NAME,
  synchronize: true,
  logging: true,
  entities: [ 'dist/**/*.entity.js'],
};
Enter fullscreen mode Exit fullscreen mode

Env variables

Notice that i don't want to expose my database password. So we need the dotenv library

Install dotenv by running this command
yarn add dotenv.

Create a .env in your root and past these credentials there.

PORT=7890
DATABASE_HOST=localhost
DATABASE_USERNAME=postgres
DATABASE_NAME=puppies
DATABASE_PASSWORD=your password here
Enter fullscreen mode Exit fullscreen mode

Lets create our data structure in the app.entity.ts

import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
} from 'typeorm';

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

  @CreateDateColumn()
  created: Date;

  @Column({
    type: 'text',
    unique: true,
    nullable: false,
  })
  name: string;

  @Column()
  age: number;

  @Column()
  breed: string;

  @Column()
  color: string;
}

Enter fullscreen mode Exit fullscreen mode

Run yarn start:dev again and let's our database connection result.

Image description


Data Transfer Objects: app.dto.ts

This is an object is an object that defines how data will be sent over the network.
Install and import class-validator

import { IsNotEmpty } from 'class-validator';

export class PuppyDTO {
  @IsNotEmpty()
  name: string;

  @IsNotEmpty()
  age: number;

  @IsNotEmpty()
  breed: string;

  @IsNotEmpty()
  color: string;
}
Enter fullscreen mode Exit fullscreen mode

Final result:

app.controller.ts

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Post,
  Put,
} from '@nestjs/common';
import { PuppyDTO } from './app.dto';
import { AppService } from './app.service';

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

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

  @Post()
  registerPuppy(@Body() data: PuppyDTO) {
    return this.appService.register(data);
  }

  @Get('/all')
  getPuppies() {
    return this.appService.readAll();
  }

  @Get(':id')
  getPuppy(id: string) {
    return this.appService.read(id);
  }

  @Put(':id')
  updatePuppy(@Param('id') id: string, @Body() data: Partial<PuppyDTO>) {
    return this.appService.update(id, data);
  }

  @Delete(':id')
  deletePuppy(@Param('id') id: string) {
    return this.appService.delete(id);
  }
}

Enter fullscreen mode Exit fullscreen mode

app.service.ts

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PuppyDTO } from './app.dto';
import { PuppyEntity } from './app.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(PuppyEntity)
    private puppyRepository: Repository<PuppyEntity>,
  ) {}
  getHello(): string {
    return 'Hello puppies!';
  }

  async register(data: PuppyDTO): Promise<PuppyDTO> {
    const puppy = await this.puppyRepository.create(data);
    await this.puppyRepository.save(puppy);
    return puppy;
  }

  async read(id: string): Promise<PuppyDTO> {
    const puppy = await this.puppyRepository.findOne({
      where: {
        id,
      },
    });
    if (!puppy) {
      throw new HttpException('Puppy not found', HttpStatus.NOT_FOUND);
    }
    return puppy;
  }

  async readAll(): Promise<PuppyDTO[]> {
    const puppies = await this.puppyRepository.find({});
    return puppies;
  }

  async update(id: string, data: Partial<PuppyDTO>): Promise<PuppyDTO> {
    let puppy = await this.puppyRepository.findOne({
      where: {
        id,
      },
    });
    if (!puppy) {
      throw new HttpException('Puppy not found', HttpStatus.NOT_FOUND);
    }
    await this.puppyRepository.update(id, data);
    puppy = await this.puppyRepository.findOne({
      where: {
        id,
      },
    });
    return puppy;
  }

  async delete(id: string) {
    const puppy = await this.puppyRepository.findOne({
      where: {
        id,
      },
    });
    if (!puppy) {
      throw new HttpException('Puppy not found', HttpStatus.NOT_FOUND);
    }
    await this.puppyRepository.delete({ id });
    return puppy;
  }
}
Enter fullscreen mode Exit fullscreen mode

Test all endpoints using postman

Homepage

localhost:7890/

Image description

POST Create profile for a puppy

localhost:7890/puppies

Image description

GET All puppies

localhost:7890/puppies/all

Image description


GET single puppy

localhost:7890/puppies/:id

Image description


DELETE puppy profile

localhost:7890/puppies/:id

Image description


UPDATE puppy profile

localhost:7890/puppies/:id

Image description


Conclusion:

I hope this was helpful in starting out with NestJs. Thanks for reading.

Resources

Nest js

Discussion (6)

Collapse
pblgllgs profile image
pbl.gllgs

thanks, i needed learn this

Collapse
drsimplegraffiti profile image
Abayomi Ogunnusi Author

Glad it was helpful

Collapse
drsimplegraffiti profile image
Abayomi Ogunnusi Author

@amrelmohamady 😃😃😃😃 okay i will do for cats too

Collapse
drsimplegraffiti profile image
Abayomi Ogunnusi Author

@andrewbaisden... You are very correct and thanks for the contribution

Collapse
andrewbaisden profile image
Andrew Baisden

Cool tutorial I like how easy it is to build an API using NestJS. Most of the setup is done for you whereas when using Express you have to create the whole project architecture from scratch.

Collapse
amrelmohamady profile image
Amr Elmohamady

You can't do this Nestjs is for cats only no puppies!