Crud Api w/ Server Side Pagination with NestJS & Angular - Initial Setup


One of the new technologies I’ve been very interested in has been the Nest.JS framework. Nest.JS is a wrapper around Express (or Fastify) that provides a ton build in features that would otherwise require some configuration/boilerplate code. In the next few blog post I’m going to build out a very simple CRUD api with support for pagination. We’re also going to use Angular to build a simple table showing our paginated endpoint in use.

 

Prerquisites

The following dependencies are required for this project.

  1. NodeJS
  2. Angular CLI
  3. NestJS CLI

 

Setup

We’re going to create a new NestJS project for our server and an Angular project for our frontend. You can either run the below commands…

mkdir nestjs-angular-server-side-pagination-example
cd nestjs-angular-server-side-pagination-example
ng new client
nest new server

Or clone the base repository with…

git clone https://github.com/jmw5598/nestjs-angular-server-side-pagination-example.git
git checkout part1/setup/start

 

Configuring TypeORM with Sqlite

For our database we are going to setup a SQLite database with TypeORM. If you prefer to use another database refer to the NestJS docs and [TypeORM docs][9] for configuration information. First we’ll install our dependencies…

npm install --save @nestjs/typeorm typeorm sqlite3 dotenv class-validator class-transformer

By default the Nest CLI will generate an AppController and an AppService both of which we will not need so we can delete those files and update our AppModule. Aside from removing the component and service, we can now add our TypeOrmModule import in the AppModule as well.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot()
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Next we need to setup our .env file to tell NestJS how to connect to our Sqlite database. You can refer to this link for a full list of TypeORM environment variables.

First create the .env file at the root of the server project. We then need to add the following values…

#General
ENV=dev
PORT=3000

# Database
TYPEORM_CONNECTION = sqlite
TYPEORM_DATABASE = mockdb
TYPEORM_SYNCHRONIZE = true
TYPEORM_LOGGING = true
TYPEORM_ENTITIES=dist/**/*.entity.js
TYPEORM_ENTITIES_DIR=src/**/*.entity.ts
TYPEORM_MIGRATIONS=dist/database/migrations/*.js
TYPEORM_MIGRATIONS_DIR=src/database/migrations

We’ll also update our package.json file with additional script to create, generate, run, and revert our TypeORM migrations…

{
  ...
  "scripts": {
    ...
    "migration:create": "ts-node node_modules/.bin/typeorm migration:create",
    "migration:generate": "ts-node node_modules/.bin/typeorm migration:generate",
    "migration:run": "ts-node node_modules/.bin/typeorm migration:run",
    "migration:revert": "ts-node node_modules/.bin/typeorm migration:revert"
  }
  ...
}

One final change we need to make is the the main.ts file under our src directory. We need to add the ValidationPipe and enable cors. The validation pipe allows us to use decorators to validator our HTTP request bodies before they hit our controllers.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  app.enableCors();
  await app.listen(process.env.PORT || 3000);
}
bootstrap();

 

Creating Our Users Module

One thing you will notice about NestJS is that it is very Angular-like. It heavily makes use of decorators, modules, and dependency injection just like Angular so if you’ve work with Angular before, NestJS will look very familiar. Endpoints are encapsulated in modules and all like file are kept with their associated modules.

For our users endpoint we are going to generate a users module, controller, service, and entity…

nest generate module users
nest generate controller users
nest generate service users
touch src/users/user.entity.ts

And we will update our UsersModule importing the TypeOrmModule registering our User entity…

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './user.entity'

@Module({
  imports: [
    TypeOrmModule.forFeature([User])
  ],
  controllers: [
    UsersController
  ],
  providers: [
    UsersService
  ]
})
export class UsersModule {}

And finally we will import our UsersModule in our AppModule (if you used the NestJS CLI to generate your UsersModule this should already be imported). This will make our endpoints in our UsersController available in our API…

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    TypeOrmModule.forRoot(),
    UsersModule
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

 

Creating Our Entity & Generating Our First Migration

Our user entity will be minimal consisting of a a first name, last name, and email.

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

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  public id: number;

  @Column({ nullable: false })
  public firstName: String;

  @Column({ nullable: false })
  public lastName: String;

  @Column({ nullable: false })
  public email: String;
}

Now that we have our entity we can generate our first migraiton. First we have to build our project and then we can run our migration:generate script that we added to our package.json file.

npm run build
npm run migration:generate -- -n "InitialMigrationForUsersEntity"

This will create a database/migrations directory under our src directory. If you look at our first migration there are two methods, up and down. The up method will run when we run our migration and down when we revert it. Inside our up method we can see the SQL that will create a user table for our User entity.

Lets run our first migration…

npm run build
npm run migration:run

Now we need to create an emptry migration. This is slightly different than the first migration we created as it doesn’t look for changes to project and auto generate a migration. This will create a completely empty migration file for us with empty up and down method. We will use this migration to seed our user table with mock user data.

npm run build
npm run migration:create -- -n "SeedUsersTableWithMockData"

Now we need to open up our empty migration and add the following migration…

import {MigrationInterface, QueryRunner, getRepository, Repository} from "typeorm";
import {User} from '../../users/user.entity';
import {MOCK_USERS} from '../seeds/users.seed';


export class SeedUsersTableWithMockData1602869450860 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    const usersRepository: Repository<User> = getRepository(User);
    MOCK_USERS.forEach(u => {
      const user: User = usersRepository.create({ ...u });
      usersRepository.save(user);
    });
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    queryRunner.query("DELECT FROM user WHERE id > 0");
  }
}

Our migration will loop through our MOCK_USERS data, use a Repository to create a new User and insert (save) each user to the database.

Now we need to create our seed data. I used a tool called Mockaroo to generate 30 mock users for our database.

First we’ll create a seeds directory inside src/database and create our first seed file…

mkdir -p src/database/seeds
touch src/database/users.seed.ts

In our seed file we can add our mock users…

import { User } from '../../users/user.entity';

export const MOCK_USERS: Partial<User>[] = [
  { firstName:"Howey", lastName:"Cockburn", email:"hcockburn0@hp.com" } as Partial<User>,
  { firstName:"Stephie", lastName:"Drowsfield", email:"sdrowsfield1@usatoday.com" } as Partial<User>,
  { firstName:"Julita", lastName:"Thomann", email:"jthomann2@thetimes.co.uk" } as Partial<User>,
  { firstName:"Anthiathia", lastName:"Ewbanke", email:"aewbanke3@privacy.gov.au" } as Partial<User>,
  { firstName:"Gaby", lastName:"Tregien", email:"gtregien4@ehow.com" } as Partial<User>,
  { firstName:"Reidar", lastName:"McTavish", email:"rmctavish5@weather.com" } as Partial<User>,
  { firstName:"Tiler", lastName:"Fagge", email:"tfagge6@smugmug.com" } as Partial<User>,
  { firstName:"Falkner", lastName:"Paoli", email:"fpaoli7@geocities.jp" } as Partial<User>,
  { firstName:"Anthe", lastName:"Heed", email:"aheed8@networksolutions.com" } as Partial<User>,
  { firstName:"Grayce", lastName:"Tollit", email:"gtollit9@timesonline.co.uk" } as Partial<User>,
  { firstName:"Janek", lastName:"Milne", email:"jmilnea@cam.ac.uk" } as Partial<User>,
  { firstName:"Ingram", lastName:"Fairbeard", email:"ifairbeardb@domainmarket.com" } as Partial<User>,
  { firstName:"Jephthah", lastName:"Thorp", email:"jthorpc@parallels.com" } as Partial<User>,
  { firstName:"Ashby", lastName:"Holston", email:"aholstond@deviantart.com" } as Partial<User>,
  { firstName:"Calla", lastName:"Pietesch", email:"cpietesche@yellowpages.com" } as Partial<User>,
  { firstName:"Marris", lastName:"Kinforth", email:"mkinforthf@ucsd.edu" } as Partial<User>,
  { firstName:"Prissie", lastName:"Vergine", email:"pvergineg@forbes.com" } as Partial<User>,
  { firstName:"Valene", lastName:"Lownds", email:"vlowndsh@geocities.com" } as Partial<User>,
  { firstName:"Norina", lastName:"Archard", email:"narchardi@multiply.com" } as Partial<User>,
  { firstName:"Brinn", lastName:"Giacopetti", email:"bgiacopettij@utexas.edu" } as Partial<User>,
  { firstName:"Ev", lastName:"Bolden", email:"eboldenk@shareasale.com" } as Partial<User>,
  { firstName:"Wyn", lastName:"Foxall", email:"wfoxalll@furl.net" } as Partial<User>,
  { firstName:"Alexandre", lastName:"Everett", email:"aeverettm@ucoz.ru" } as Partial<User>,
  { firstName:"Keane", lastName:"Holbie", email:"kholbien@unicef.org" } as Partial<User>,
  { firstName:"Jana", lastName:"Abbott", email:"jabbotto@xinhuanet.com" } as Partial<User>,
  { firstName:"Ansley", lastName:"Shitliffe", email:"ashitliffep@joomla.org" } as Partial<User>,
  { firstName:"Ralf", lastName:"Filippello", email:"rfilippelloq@twitpic.com" } as Partial<User>,
  { firstName:"Clay", lastName:"Belding", email:"cbeldingr@live.com" } as Partial<User>,
  { firstName:"Kingsly", lastName:"Frearson", email:"kfrearsons@upenn.edu" } as Partial<User>,
  { firstName:"Boyd", lastName:"Tisun", email:"btisunt@yale.edu" } as Partial<User>
]

Now all we have to do is build and run our new seed migration…

npm run build
npm run migration:run

This should create a mockdb SQLite database file at the root of our server folder with our user table populated with our mock users.

 

Conclusion

Next we will create a basic Crud API to create, read, update and delete users. Later we will update our API to generate server side pagination when retrieving a list of users.

 

Final Github Repository

 


comments powered by Disqus