A progressive Node.js framework for building efficient and scalable server-side applications.
Nest framework TypeScript starter repository.
$ npm install# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:covNest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
- Author - Kamil Myśliwiec
- Website - https://nestjs.com
- Twitter - @nestframework
Nest is MIT licensed.
install nest cli
nest new [project name]
cd into [project name]
pnpm run start
pnpm run start:dev
Controllers handle requests in our application.
nest generate controller [name]
(will auto wire up with app module for us).
nest generate controller modules/[name]
(will generate in src/modules/[name]/)
can use --dry-run flag to see simulated output in console:
nest generate controller modules/[name] --dry-run
Services handle business logic in our application.
nest generate service [name]
(will auto wire up with app module for us).
nest generate service modules/[name]
(will generate in src/modules/[name]/)
can use --dry-run flag to see simulated output in console:
nest generate service modules/[name] --dry-run
** A service is a provider
A provider means it can inject dependencies
** A provider is just a class annotataed with a decorator called Injectable...
SERVICE:
@Injectable()
export class CoffeesService {}IT IS USED HERE IN THE CONTROLLER:
@Controller('coffees')
export class CoffeesController {
constructor(private readonly coffeesService: CoffeesService) {}
}Nest will resolve the coffeesService by creating and returning an instance of CoffeesService to our CoffeesController.
Or in the case of a singleton - returning the existing instance if has already been requested elsewhere.
nest generate module [name]
nest generate class coffees/dto/create-coffee.dto --no-spec
nest generate class coffees/dto/update-coffee.dto --no-spec
We use --no-spec flag to not generate a test file for our dto...
look at the coffee entity to see what properties we will need for the dto.
First install the following:
pnpm install class-validator class-transformer
Then add validation pipe in place:
app.useGlobalPipes(new ValidationPipe());pnpm install @nestjs/mapped-types
Avoid repetition code smell by using PartialType:
import { PartialType } from '@nestjs/mapped-types';
import { CreateCoffeeDto } from './create-coffee.dto';
export class UpdateCoffeeDto extends PartialType(CreateCoffeeDto) {}In UpdateCoffeeDto all fields are optional.
{
whitelist: true,
forbidNonWhitelisted: true,
}Example request body (isEnabled does not exist on Dto)...
POST localhost:3000/coffees
{
"name": "Shipwreck Roast",
"brand": "Buddy Brew",
"flavours": ["caramel"],
"isEnabled": true
}Response back:
{
"statusCode": 400,
"message": ["property isEnabled should not exist"],
"error": "Bad Request"
}Port mapping example:
ports:
- '5432:5432'Postgres will be running on its default port 5432 INSIDE the docker container But we will also be able to access Postgress from OUTSIDE container on port 5432 aswell.
docker-compose up -d will spawn all services
docker-compose up db -d will spawn just the service named db
-d is detached mode - which means run containers in background
Install below three packages;
pnpm install @nestjs/typeorm typeorm pg
TypeORM support Repository design pattern
Each Entity we create has its own repository...
Since we created a Coffee Entity, we can now inject the automatically generated repository into our coffees service using the InjectRepository decorator
Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise. Example:
return coffee;
return Promise.resolve(coffee);Throwing exception in async func will automatically return a rejected promise:
return Promise.reject(`Coffee #${id} not found`);nest g class coffees/entities/flavour.entity --no-spec
(don't need test file for entities)
Notes:
@JoinTable()Specifies the OWNER side of the relationship
@ManyToMany((type) => Flavour, (flavour) => flavour.coffees)First param just establishes what the TYPE for the relation is. Its just a function that returns a reference to the related entity.
Second param - returns the related entity and specifies what property needs to be selected that is the inverse side of the relationship... In other words - what is coffee inside of the Flavour entity...
IMPORTANT - A new table will be created: coffee_flavours_flavour which represents the ManyToMany relation between Coffee and Flavour entities
So in total we have three tables:
coffee coffee_flavours_flavour flavour
Also remember relations are not eagerly loaded wheh making a request for data...
We need to specify the relation we want to include when getting data back like so:
const coffee = await this.coffeeRepository.findOne({
where: { id: id },
relations: ['flavours'],
});When creating a new coffee with a flavour/s, automatically insert the flavours into the flavour table:
@ManyToMany((type) => Flavour, (flavour) => flavour.coffees, {
cascade: true,
})nest generate class common/dto/pagination-query.dto --no-spec
To test:
localhost:3000/coffees?limit=1 - should return just one result
localhost:3000/coffees?offset=1 - skips first coffee and we get back all the rest of the results from db
nest generate class events/entities/event.entity --no-spec
Indexes are special lookup tables, that our db search engine can use to speed up data retrieval.
Indexes help give our application rapid random lookups and efficient access of ordered records, use them whenever performance is vitaly important for a certain entity.
typeorm config file (ormconfig.ts):
import { DataSource } from 'typeorm';
export const typeOrmConnectionDataSource = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'xxxx',
password: 'xxxx',
database: 'postgres',
entities: ['dist/src/**/*.entity.js'],
migrations: ['dist/src/migrations/*.js'],
});The config setiings above are the same we used in our docker-compose file with a few additional values used to let typeorm migrations know where our entities and migration files will be.
** TYPEORM MIGRATIONS NEED TO WORK ON COMPILED FILES, which nest will output in the dist folder **
Typeorm provides us with a dedicated CLI we can use.
** npx lets us use executable packages without having to install them, this lets us use the typeorm CLI without actually installing it on our machine. **
- Edit the Entity (add new column or rename an existing column etc etc) and save file.
Example add a new description column in Coffee Entity:
@Column({ nullable: true })
description: string;-
Compile code using:
pnpm run buildTYPEORM MIGRATIONS NEED TO WORK ON COMPILED FILES, which nest will output in the dist folder. -
Now let typeorm generate a migration for us:
typeorm migration:generate ./src/migrations/[name of migration depending on what your doing] -d ./dist/ormconfig.jsA migration .ts file will be added to ./src/migrations
-dflag refers to datasource... -
Compile code again so this time we have the migrations folder compiled to dist/src/migrations
pnpm run build -
Run the migration
npx typeorm migration:run -d ./dist/ormconfig.js -
To revert the migration do:
npx typeorm migration:revert -d ./dist/ormconfig.js
Understand encapsulation...
Generate a module
nest g mo coffee-rating
Generate a service
nest g s coffee-rating
Lets say our new CoffeeRatingService depends on CoffeeService to fetch Coffee's from the db...
Asynchronous Providers...
Sometimes when our application is bootstrapped, we need to delay the entire process until one or more async tasks have completed. For example, we do not want to accept requests until the connection with our database has been established.
We will see our CoffeeService class is instantiated AFTER COFFEE BRANDS are returned from the database. Can check by looking at console logs.
console.log('[!] Async factory');
//followed by
console.log(coffeeBrands); /* in the CoffeeService class constructor function */nest g mo database --no-spec
Very important - sometimes we need to configure a module from a consuming module.
DEFAULT BEHAVIOUR:
@Injectable()is really a shorthand implementation for
@Injectable({ scope: Scope.DEFAULT})Since generally by default all providers are Singletons (classes created once and cached for use when application is first bootstrapped)
As a best practice, using Singleton scope is recommended for most use cases... mainly for performance reasons.
Transient and Request scope lifetimes...
-
Transient providers are not shared across consumers, each consumer that injects a transient provider will receive a new dedicated instance of that provider.
@Injectable({ scope: Scope.TRANSIENT})
-
Request scope, provides a new instance of the provider exclusively for each incoming request. The instance is also automatically garbage collected after request has finished processing.
@Injectable({ scope: Scope.REQUEST})
Request scope providers can inject their original request object - useful if you need headers, cookies, ip addresses etc... However, this may have an impact on app performance, since nest has to create a newinstance of the class for every request
@Inject(REQUEST) private readonly request: Request,
HOWEVER, always recommended to use the default Singleton Scope whenever possible.
pnpm install @nestjs/config
IMPORTANT - NORMALLY SHOULD NOT ADD .env file to gitcontrol...
Add *.env in .gitignore file
Install Joi:
pnpm install joi
pnpm install --save-dev @types/joi