To connect a NestJS application to an SQL database, we are going to use typeorm which has a built-in integration with NestJS.
The official documentation for typeorm integration with your NestJS application is pretty lengthy! Let’s hover the main points to get you started. If you are not familiar with NestJS, check that article for a quick introduction.
Installation
Add the typeorm dependency to your NestJS project:
npm i --save @nestjs/typeorm typeorm pg
We are using postgres for our example, but typeorm supports many other databases. This will let me go a bit further than the official documentation with examples for a real setup.
Create the entity
From the code
With typeorm, you create the tables and columns directly from the code using the entity classes.
Let’s create a User
entity:
import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn } from 'typeorm';
@Entity('nest_users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500, nullable: true })
name: string;
@CreateDateColumn({ type: 'timestamp' })
created_at: Date;
}
In this example, we are doing a couple of things, let’s describe it:
@Entity('nest_users')
: This will create a table namednest_users
in the database.@PrimaryGeneratedColumn()
: This will create a primary key that is auto-incremented.- Each new user entity created will have a new id when saved in the database.
- This is the equivalent of
SERIAL
in postgres, to have auuid
instead of a number use:@PrimaryGeneratedColumn('uuid')
. - You can also use
@PrimaryColumn()
if you want to define the primary key yourself.
@Column({ length: 500, nullable: true })
: This will create a column in the table with a length of 500 characters that can be null.- The arguments are optional and can be omitted.
- The name of the column is
name
from the variable, but you could also change it passing the name as an argument:@Column({ name: 'first_name' })
.
@CreateDateColumn({ type: 'timestamp' })
: This will create a column in the table with the current timestamp when the entity is created.- For updates, you can find the equivalent called:
@UpdateDateColumn
. - You can set different types of dates, like
date
,time
,timestamptz
, etc.
- For updates, you can find the equivalent called:
I find it similar to the equivalent Java library hibernate with the annotations.
Persist the table
Configuration
Now that the entity is created, we need to persist it in the database. For that, you will need to have postgres database running matching and the proper configuration.
I created a file called datasource.ts
that will contain the configuration for the database connection:
const dataSourceOpts: DataSourceOptions = {
type: 'postgres',
host: process.env.TYPEORM_HOST,
port: Number(process.env.TYPEORM_PORT),
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
synchronize: true,
entities: [join(process.cwd(), 'src/**/*.entity.ts')],
migrations: [join(process.cwd(), 'migrations/*.ts')],
logging: false,
};
const dataSource = new DataSource(dataSourceOpts);
export default
In this setup, most of the information is passed through environment variables, to avoid “hardcoding” the database credentials which could be sensitive information.
- The
entities
option is to let typeorm know where to find the entities to persist in the database. - The
migrations
option refers to the different changes that happened to your database schema over time.
In your package.json
, define the typeorm command (replace ts-node
by node
if you are not using typescript).
{
"scripts": {
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js -d src/datasource.ts"
}
}
And now you should be all set to create the tables in the database.
Create the tables
The entity is not saved in the database until you create a migration and run it against the database.
A migration is a file that contains the changes between the defined entities in your code and the current database schema structure. To generate a migration, you can run:
yarn typeorm migration:generate migrations/AddInitialTables
This will create a new file with a timestamp in the migrations
folder.
The entity is still not persisted yet, you will now need to run that migration in order to commit the change to the database:
yarn typeorm migration:run
You should see the tables created in your database.
Once committed, the migration tables should not be updated, if a change occurs, a new migration should be created. Which means, everytime you modify an entity in the code, a migration should be created and committed.
😬 I messed up the database how do I reset it?
If you have made a mistake or the database is in an odd state (maybe due to a test with your local setup), you can drop the schema. Then you can rerun the migration to recreate the tables:
yarn typeorm schema:drop
⚠️ Don’t drop the schema in production, you will lose all your data!
Interact with the database
Set up the application
Now that the tables are created, you can start using the repository to interact with the database.
In your Application module (usually) you can add the TypeOrmModule
to the setup the connection for the application.
@Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () => ({
...datasource.options,
migrations: [],
entities: [],
autoLoadEntities: true,
}),
}),
// ...other imports
],
// ...other configurations
})
export class ApplicationModule {}
The connection used for the application is based on the one used to run the typeorm command from the cli.
With this setup, interacting with entities in the other modules will be possible.
The autoLoadEntities: true
will load the entities specified by the modules interacting with the database.
Importing the repository
In your module, you can import the repository to use it in your services via the TypeOrmModule.forFeature
method
which will load the repository to access the selected entities.
We can have a UserModule
that will import the UserRepository
to interact with the User
entity represented by its
postgres table:
@Module({
imports: [
TypeOrmModule.forFeature([User]),
],
providers: [UserResolver, UserService],
})
export class UserModule {}
It looks similar to the previous example because it uses the same TypeOrmModule
class, but the forFeature
method
is different and is using the forRoot
or forRootAsync
method used in the application module,
to be able to connect to the database.
Using the repository
Now that we have the module setup, we can create our user service. A service that can be used to retrieve the users or create a new one (possibly in association with a REST or GraphQL API).
export class UserService {
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
) {}
async createUser(name: string): Promise<User> {
return await this.userRepository.save({ name });
}
async getUsers(): Promise<User[]> {
return this.userRepository.find();
}
}
Let’s describe the User service to see how we interact with the database:
- The
@InjectRepository(User)
is used to inject theUserRepository
into the service.- This repository allows us to interact with the User’s table in the database
- We use the
save
method to insert a new user in the database.- We could use
const user = new User()
, set the name and thenthis.userRepository.save(user)
. - ⚠️ The
userRepository.create
makes a user entity copy, but it does not insert anything in the database.
- We could use
- We use the
find
method to retrieve all the users from the database.- There are other find methods like
findOne
,findByIds
,findAndCount
, etc.
- There are other find methods like
You can find more advanced usage of the repository in the typeorm official documentation. This should be enough for now to get you started! 🎉