Appearance
TypeORM
TypeORM is our preferred way to interact with the database.
We can define an entity class which will function as an abstract layer on top of a database-table. TypeORM will automatically generate migrations for us, which will create the tables in the database.
Connection
In the src/sql/sources/main.ts you can find the DataSource that defines the connection with the main Postgres database.
Config
An explanation of chosen config:
logging: Set this totruefor logging all executed queries.entities: An array with a pattern that matches all entities in the project.migrations: An array with a pattern that matches all migrations in the project.
All other options should be left as default.
Entities
An entity is a class that maps to a database table. You can create an entity by defining a new class and mark it with @Entity().
Configuring an entity is explained here.
Migrations
Creating a migration
As said before migrations are automatically generated by TypeORM from the entities and their decorators.
You can create a migration by running:
bash
pnpm typeorm migration:generate src/sql/migrations/<migration-name>This will create a new migration file in the src/sql/migrations folder containing your changes.
Running a migration
Locally
To run a migration locally, you can run the following command
bash
pnpm typeorm migration:runOn the infrastructure
Migrations are automatically run before the application is deployed.
Locking the database
When running migrations on the infrastructure, the database will be locked. This means that no other migrations can be executed on the database. This is done to prevent data corruption.
In case a migration fails, the database will be locked until the issue is manually resolved. Contact the DevOps team in case of a failed migration.
Repositories
TypeORM offers 2 patterns to interact with your entities. Our preferred way of working is using repositories. We create a repository class which extends our custom TypeOrmRepository.
Read more about repositories here.
QueryBuilder
For some more advanced applications, a query builder can be used.
High Availability and Read Replicas
Our database setup consists of a single read/write database with a high availability node. Optionally one or multiple read-only databases can be configured too.
You need this when you have a high amount of read operations and you want to scale horizontally.
You can contact the DevOps team to set this up.
Using the readonly helper
Use the readonly(dataSource, fn) helper from @wisemen/nestjs-typeorm to route queries to a read replica inside the provided callback.
ts
import { readonly } from "@wisemen/nestjs-typeorm";
await readonly(dataSource, async () => {
// All repository calls in here use a replica connection
return userRepository.find({ where: { isActive: true } });
});Notes:
- Readonly and transaction contexts are mutually exclusive.
- Replication lag means very recent writes may not be visible immediately.
- Repositories/services already route automatically based on the active context.
See also: Readonly contexts and the Transactions section below for details.
Transactions
Use the transaction(dataSource, fn) helper from @wisemen/nestjs-typeorm to group multiple operations into a single transaction.
ts
import { transaction } from "@wisemen/nestjs-typeorm";
await transaction(dataSource, async (em) => {
await em.save(MyEntity, entity);
});Readonly and transaction contexts cannot be nested within each other. See transactions for details, exclusivity rules, and more examples.
