Skip to content

Developer Insights

Join millions of viewers! Our engineers craft human-written articles solving real-world problems weekly. Enjoy fresh technical content and numerous interviews featuring modern web advancements with industry leaders and open-source authors.

Newest First
Setting Up TypeORM Migrations in an Nx/NestJS Project cover image

Setting Up TypeORM Migrations in an Nx/NestJS Project

TypeORM is a powerful Object-Relational Mapping (ORM) library for TypeScript and JavaScript that serves as an easy-to-use interface between an application's business logic and a database, providing an abstraction layer that is not tied to a particular database vendor. TypeORM is the recommended ORM for NestJS as both are written in TypeScript, and TypeORM is one of the most mature ORM frameworks available for TypeScript and JavaScript. One of the key features of any ORM is handling database migrations, and TypeORM is no exception. A database migration is a way to keep the database schema in sync with the application's codebase. Whenever you update your codebase's persistence layer, perhaps you'll want the database schema to be updated as well, and you want a reliable way for all developers in your team to do the same with their local development databases. In this blog post, we'll take a look at how you could implement database migrations in your development workflow if you use a NestJS project. Furthermore, we'll give you some ideas of how nx can help you as well, if you use NestJS in an nx-powered monorepo. Migrations Overview In a nutshell, migrations in TypeORM are TypeScript classes that implement the MigrationInterface interface. This interface has two methods: up and down, where up is used to execute the migration, and down is used to rollback the migration. Assuming that you have an entity (class representing the table) as below: ` If you generate a migration from this entity, it could look as follows: ` As can be seen by the SQL commands, the up method will create the post table, while the down method will drop it. How do we generate the migration file, though? The recommended way is through the TypeORM CLI. TypeORM CLI and TypeScript The CLI can be installed globally, by using npm i -g typeorm. It can also be used without installation by utilizing the npx command: npx typeorm . The TypeORM CLI comes with several scripts that you can use, depending on the project you have, and whether the entities are in JavaScript or TypeScript, with ESM or CommonJS modules: - typeorm: for JavaScript entities - typeorm-ts-node-commonjs: for TypeScript entities using CommonJS - typeorm-ts-node-esm: for TypeScript entities using ESM Many of the TypeORM CLI commands accept a data source file as a mandatory parameter. This file provides configuration for connecting to the database as well as other properties, such as the list of entities to process. The data source file should export an instance of DataSource, as shown in the below example: ` To use this data source, you would need to provide its path through the -d argument to the TypeORM CLI. In a NestJS project using ESM, this would be: ` If the DataSource did not import the Post entity from another file, this would most likely succeed. However, in our case, we would get an error saying that we "cannot use import statement outside a module". The typeorm-ts-node-esm script expects our project to be a module -- and any importing files need to be modules as well. To turn the Post entity file into a module, it would need to be named post.entity.mts to be treated as a module. This kind of approach is not always preferable in NestJS projects, so one alternative is to transform our DataSource configuration to JavaScript - just like NestJS is transpiled to JavaScript through Webpack. The first step is the transpilation step: ` Once transpiled, you can then use the regular typeorm CLI to generate a migration: ` Both commands can be combined together in a package.json script: ` After the migrations are generated, you can use the migration:run command to run the generated migrations. Let's upgrade our package.json with that command: ` Using Tasks in Nx If your NestJS project is part of an nx monorepo, then you can utilize nx project tasks. The benefit of this is that nx will detect your tsconfig.json as well as inject any environment variables defined in the project. Assuming that your NestJS project is located in an app called api, the above npm scripts can be written as nx tasks as follows: ` The typeorm-generate-migration and typeorm-run-migrations tasks depend on the build-migration-config task, meaning that they will always transpile the data source config first, before invoking the typeorm CLI. For example, the previous CreatePost migration could be generated through the following command: ` Conclusion TypeORM is an amazing ORM framework, but there are a few things you should be aware of when running migrations within a big TypeScript project like NestJS. We hope we managed to give you some tips on how to best incorporate migrations in an NestJS project, with and without nx....

Introducing the express-typeorm-postgres Starter Kit cover image

Introducing the express-typeorm-postgres Starter Kit

At This Dot, we've been working with ExpressJS APIs for a while, and we've created a starter.dev kit for ExpressJS that you can use to scaffold your next backend project....

Connecting to PostgreSQL using TypeORM cover image

Connecting to PostgreSQL using TypeORM

In my previous article we learned how to connect to a PostgreSQL database using the node-postgres package pg. This works fine, and will be perfect for many applications, but one may also choose to opt for using an ORM instead. What is an ORM? ORM stands for object-relational-mapping and allows you to interact with your database using objects from your programming language's type system rather than hand writing queries. This allows ORMs to simplify your logic that manipulates data in the database. In this article, we've opted to use TypeORM as it is widely used, and it supports many of the advanced features expected from an ORM framework. Setup We'll be demonstrating examples with both Data Mapper and Active record patterns. Both the Data Mapper and Active Record patterns are supported by TypeORM, and we'll cover the former first. Data Mapper allows you to represent tables and other data as Entity classes. You can then define properties in these classes that map to the respective columns in the database, and even include custom methods in the entities that manipulate them. For starters, you'll want to have TypeScript and TypeORM installed. TypeORM uses TypeScript exclusive features for defining your entities, and although it's not required to use TypeORM, it makes it much easier to do so. TypeScript is an extension to JavaScript that adds a strict type system. At the time of writing this article, TypeORM requires TypeScript v3.3 at a minimum, and this is subject to change. ` Now, TypeORM has to be installed! This can be done with the following npm command in your project root: ` Let's define a database and a simple schema with a people tables like we did in our node-postgres article. Please check out that article if you need instructions on how to set up a PostgreSQL to develop against. ` Data Mapper The Data Mapper pattern allows you to define Entities that represent your data types in the database, and repositories to store your query methods and other domain logic. Entities in Data Mapper are very simple. The following example is how an Entity for our person table is defined using the Data Mapper pattern. This is contained in a file called person.ts. ` Note that this Entity uses decorators that are experimental at the time of writing this article. Make sure you set experimentalDecorators and emitDecoratorMetadata in your tsconfig.json for this to work. You can generate a tsconfig.json by running tsc --init. Let's break down what these decorators and their options mean. @Column: Put above a property that maps to a column. The name of the property is assumed to be the name of the column in the table. If the name in the Entity doesn't match the database column name, you can specify a name in an options object in the decorator. @PrimaryGeneratedColumn(): This signals to TypeORM that this is a PRIMARY KEY column that uniquely represents the entity. To write a query method, we'll need to create a repository. A repository is a class that contains query methods and other helpers. Domain logic that works with Entities is separated from the definition of the data types themselves. This will go in people-repository.ts. ` The following code can be used to set up a connection and interact with the database with our Entity and Repository. It's a better practice to change the fullname property on the class instance and use save() to update it in the database. However, I wanted to demonstrate how the query builder can be used for UPDATE queries as well. We put this in our main index.ts file. ` Now, with all the code in place, it can be executed with tsc && node index.js. The above demos a basic CRUD flow just like we do in our previous article where we use raw SQL. All methods for interacting with the data exist in the Repository class, and this class has our own custom functions and pre-existing functions for saving and removing data that we use. Active Record The Active Record approach allows you to define query methods in the model itself, rather than doing it in a repository like we did with the Data Mapper pattern. You may have used the Active Record pattern before if you've ever used Ruby on Rails. For our example, there won't be very many changes. We can actually use the same Entity as we did in the Data Mapper class, however we will move our query methods into this class. We also make one of the queries static so we don't need an instance of the class to call the method. The name update method can remain an instance method as it can utilize the id property in an instance to simplify making calls to it. Here is the new person.ts. ` The code in index.ts is similar to the data mapper approach, but differs in how the query methods are called. Since we don't have a repository class with Active Record, we just call the methods directly on the Entity instance. ` Both methods are valid, and you should choose whatever you're more comfortable with, and whatever makes the most sense for your project. Conclusion TypeORM is a useful tool, and is capable of so much more than what is showcased here. We've only skimmed the surface of what TypeORM provides, and there is so much more. There is support for many more advanced features such as lazy relations, migrations, pagination, etc. The full documentation on their website has a lot of useful information. You can find all the source code in this GitHub repo!...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co