Hello, my friend! ๐
Nice to have some company on this quest to learn NestJS.
Intro
If you have been following this series, skip the intro!
This is the fourth part of a series about learning NestJs for people who have experience building web applications but in languages other than typescript.
The structure of these articles is not scripted ๐ฎ nor random ๐. My goal is to lay out everything I learn with as much depth as I can understand. I try to build working code that I understand and can replicate in upcoming real projects. That's why I don't write about any foo bar examples.
This is my learning process ๐... hopefully it will serve you as well.
The articles are written at the same time as I code and read the docs for what I am trying to build. After the draft is completed, I revise it, edit it, and reorder stuff to make reading this article easier for you and me in the future.
Recap
Last time we implemented a semi-public authentication scheme. We covered how to create a JWT token and validate it using the Passport library and guarding our endpoints with custom guards extending from the NestJS Passport wrapper.
The aim now is to add users and close the authentication flow. For that, we are going to configure a database. We will leverage documentation and tutorials. Throughout this article, I will reference this YouTube video which is awesome :love:.
Let's go!
Choosing the database
Won't be spending brain power on this ๐. Let's go with PostgreSQL.
Set Up TypeORM
The official NestJs documentation refers to TypeORM for driving the database layer module. Let's take a look at the TypeORM documentation.
If influenced by Entity Framework, I already like this :love:
Classes representing entities are decorated as well as their properties which represent database columns. The data access patterns can DataMapper or ActiveRecord. The ActiveRecord pattern looks nice, but I am used to working with a DataMapper/Repository pattern. So I'll stick with it for now.
Setting up TypeORM looks pretty intuitive.
First, we need to install TypeORM, the NestJS wrapper, and the database driver for Node. In this case, we use "pg" for a PostgreSQL database.
$ npm install --save typeorm @nestjs/typeorm pg
Let's declare a User entity with a unique Id, email, name, last name, and password hash.
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export default class User {
@PrimaryGeneratedColumn('uuid')
public Id: string;
@Column()
public Email: string;
@Column()
public Name: string;
@Column()
public LastName: string;
@Column()
public PasswordHash: string;
}
TypeORM needs a DataSource configured (database connection info such as host, name, user, etc.). For that, we use the NestJS wrapper. It provides a class that should receive those configuration parameters and be imported to the root module of our application. Once that is done, we can import the repositories we need in other modules.
So, at the root AppModule we declare the TypeOrmModule with basic database configuration parameters:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthenticationModule } from './authentication/authentication.module';
import ExampleModule from './example/example.module';
import User from './model/entities/User';
@Module({
imports: [
ExampleModule,
AuthenticationModule,
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
username: 'sa',
password: 'sarasa',
database: 'test',
entities: [User],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Then in our ExampleModule, we import TypeOrmModule and register the repositories that we need. Then, at the ExampleService we can inject any of those registered repositories in the current module:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthenticationModule } from 'src/authentication/authentication.module';
import User from 'src/model/entities/User';
import ExampleController from './example.controller';
import ExampleService from './example.service';
@Module({
controllers: [ExampleController],
providers: [ExampleService],
imports: [AuthenticationModule, TypeOrmModule.forFeature([User])],
exports: [],
})
export default class ExampleModule {}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import User from 'src/model/entities/User';
import { Repository } from 'typeorm';
@Injectable()
export default class ExampleService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
) {}
async get(id: string) {
const user = await this.userRepository.findOneBy({ Id: id });
return user.Name;
}
// All other methods
// ...
}
Ok, time to get real with the database
Go to the postgreSQL site and download the installer. Create a database with the provided GUI or CLI and fill the data in the database configuration at AppModule.
Migrations are handled by TypeORM (no NestJS sugar here). In this case, the documentation is not very clear and did not work for me. I ended up throwing away all I coded because of that documentation and went back to the YouTube video I mentioned before.
2 key ideas clarified everything for me:
- The database configuration should be accessible by the runtime and the database client for managing database changes. For this, the configuration should be defined in a separate file that the CLI and the code can access.
- References to entities and the Datasource instance should be to the transpiled JavaScript files generated when NestJs builds the project.
So I wrote a configuration file and a data source file that feeds from it. You could define both things in the same file, but I like to have those things separated for clarity.
For the configuration object, I copy pasted what we defined before and added paths to the entity end migration folders. This is easier because if not we should reference every entity and migration file explicitly.
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
const ormConfig: PostgresConnectionOptions = {
type: 'postgres',
host: 'localhost',
schema: 'public',
username: 'your-data-base-user-name',
password: 'your-data-base-user-password',
database: 'your-data-base-name',
entities: ['dist/model/entities/*.entity.js'],
migrations: ['dist/model/migrations/*.js'],
synchronize: false,
};
export default ormConfig;
As mentioned before, we need a DataSource instance.
import { DataSource } from 'typeorm';
import ormConfig from './ormconfig';
const dataSource = new DataSource(ormConfig);
export default dataSource;
Done setting up the configuration for our database. Now we can test if everything works by generating some migrations and running them.
Migrations
For generating migrations and running them it is useful to write some npm scripts. I will go with what is suggested in the YouTube video.
So in your package.json
file, you can add these scripts. Make sure you reference the transpiled DataSource file for the typeorm script. Remember that the transpiled files get generated inside the dist folder. Also, notice that this script builds the project first so that all the transpiled files are updated.
{
//...
"scripts": {
"typeorm": "npm run build && npx typeorm -d dist/model/DataSource.js",
"migration:generate": "npm run typeorm -- migration:generate",
"migration:run": "npm run typeorm -- migration:run",
"migration:revert": "npm run typeorm -- migration:revert"
},
//...
}
For the migration:generate script you should pass the path to the file where the migration should be generated.
npm run migration:generate -- model/migrations/Your-Migration-Name
npm run migration:run
Wrap Up
Today we managed to set up a postgres database and generate entities and migrations. Now we can start creating Users! That is what we will be doing next.
Thanks for reading. Hope this is helpful to you.
Stay tuned!
Top comments (0)