Crud Api w/ Server Side Pagination with NestJS & Angular - Initial Setup
Published on
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.
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