Sequelize and TypeScript Integration: A Practical Tutorial
Zipy team
Posted on July 19, 2023
Author - Anom Warbhuvan
Sequelize, a widely used ORM (Object Relational Mapping) library for Node.js is embraced by developers who work with different SQL databases such as PostgreSQL, MySQL, SQLite, and SQL Server. It provides a promise-based API to perform CRUD operations, transactions, associations, validations, and more on your database tables using JavaScript or TypeScript objects.
In this blog post. We will explore how to integrate Sequelize with TypeScript. By the end of this blog you will get a firm grasp on how to utilize Sequelize alongside TypeScript in order to develop dependable and sustainable APIs.
So grab your laptops, open your IDEs, and lets start on this exciting journey.
Pre-requisites
Before we start exploring how to use Sequelize ORM with TypeScript, let us make sure you have everything you need installed and set up.
It is necessary to ensure that Node.js is properly installed on your computer. This is because Sequelize is built for Node.js applications, and it's a requirement for running the ORM. Visit the official website of Node.js and proceed latest version of the software.
Next, we will be using the npm package manager for our dependencies. Yarn is a fast and reliable package manager that's becoming increasingly popular in the JavaScript ecosystem. Make sure you have it installed on your system before continuing.
Finally, you will require a IDE or text editor. There are many great options available but VS code has many useful extensions that can enhance your coding experience and productivity.
What is Sequelize CLI
Sequelize CLI is a command-line interface that helps you create and manage your Sequelize projects. It allows you to generate models, migrations, seeders, and config files for your database.It also lets you run migrations and seeders to update and populate your database. With Sequelize CLI perform work more efficiently in your Node.js project with the flexibility of SQL databases.
Understanding Sequelize TypeScript associations
Sequelize is an ORM (Object Relational Mapping) library that provides a convenient way to interact with relational databases. When using Sequelize with TypeScript, defining associations between models can be a powerful tool to simplify database queries and improve performance. Sequelize TypeScript associations allow you to establish relationships between different tables and retrieve data from multiple tables in a single query.
To define associations between Sequelize models in TypeScript, you first need to define the model interfaces with their respective attributes and data types. Once the models are defined, you can create associations using the belongsTo, hasOne, hasMany, and belongsToMany methods, depending on the type of relationship you want to establish between the models.
For example, suppose you have a User model and a Post model, and you want to establish a one-to-many relationship between them, where each user can have multiple posts. You can define the association in TypeScript as follows:
// Define User model interface
interface UserAttributes {
id: number;
name: string;
email: string;
}
interface UserInstance extends Sequelize.Instance<UserAttributes>, UserAttributes {}
const User = sequelize.define<UserInstance>('User', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: Sequelize.STRING,
email: Sequelize.STRING,
});
// Define Post model interface
interface PostAttributes {
id: number;
title: string;
content: string;
userId: number;
}
interface PostInstance extends Sequelize.Instance<PostAttributes>, PostAttributes {}
const Post = sequelize.define<PostInstance>('Post', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
title: Sequelize.STRING,
content: Sequelize.TEXT,
userId: Sequelize.INTEGER,
});
// Define association between User and Post models
User.hasMany(Post, { foreignKey: 'userId' });
Post.belongsTo(User, { foreignKey: 'userId' });
In this example, the hasMany method establishes a one-to-many relationship between the User and Post models, and the belongsTo method defines the inverse relationship between the Post and User models. The foreignKey option specifies the name of the foreign key column that links the two tables.
Overall, defining associations between Sequelize TypeScript models can help you build more efficient and maintainable database applications by simplifying complex queries and reducing the number of database requests.
Managing database changes with Sequelize TypeScript migrations
Sequelize migrations are a powerful tool for managing database schema changes, allowing you to version control and apply changes to your database in a systematic and repeatable way. When working with Sequelize and TypeScript, migrations can be especially useful for maintaining the integrity of your database schema and keeping it in sync with your codebase.
To use migrations in a TypeScript project with Sequelize, you first need to install the sequelize-cli package and configure it to work with your database. After setting up your project, use sequelize-cli to generate migration files. These files will define the modifications you wish to apply to your database schema.
For example, suppose you want to add a createdAt and updatedAt timestamp to your User model. You can create a migration file in TypeScript as follows:
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('Users', 'createdAt', Sequelize.DATE);
await queryInterface.addColumn('Users', 'updatedAt', Sequelize.DATE);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('Users', 'createdAt');
await queryInterface.removeColumn('Users', 'updatedAt');
},
};
In this example, the up function defines the changes to be applied to the database, and the down function specifies how to undo those changes in case of a rollback. The queryInterface parameter provides a set of methods for modifying the database schema, such as addColumn, removeColumn, and many others. The Sequelize parameter gives you access to the Sequelize library's data types and utilities.
To apply the migration, you can run the following command in your terminal:
sequelize db:migrate
This command will execute all pending migrations and update your database schema accordingly. You can also use the db:migrate:undo command to revert the most recent migration or the db:migrate:undo:all command to revert all migrations.
Overall, using Sequelize TypeScript migrations can help you maintain a consistent and reliable database schema throughout the development and deployment of your application. By keeping your schema changes version controlled and repeatable, you can avoid manual errors and ensure the consistency and integrity of your data.
Let’s understand this better with the help of an example.
Sequelize Typescript example - What are we building today?
Today, we will be creating a project in Node using TypeScript and building an API using the Express.js framework. Our goal is to implement CRUD operations for our application. CRUD is an acronym for Create, Read, Update, and Delete. By utilizing these four actions valuable information can be created, accessed, modified, or removed from the designated repository. CRUD operations are often used with SQL, a language for querying and manipulating data in relational databases.
Setting up our Project
- Create a project directory and navigate into it:
mkdir ts-sequelize
cd ts-sequelize
- Initialize a TypeScript project and add the necessary dependencies:
npm init -y
npm install typescript ts-node-dev @types/node --save-dev
Here, we are initializing a new Node.js project and installing TypeScript, ts-node-dev, and @types/node as development dependencies. TypeScript is a superset of JavaScript that provides strong typing capabilities, ts-node-dev is a development server that allows us to run TypeScript files without compiling them to JavaScript first, and @types/node provides TypeScript definitions for Node.js.
- Create a tsconfig.json file and add the necessary configuration to it:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": [
"esnext"
],
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "esnext",
"moduleResolution": "Node"
}
}
Here, we are configuring TypeScript to generate source maps, output compiled files to a dist directory, enable strict type checking, allow the use of ES6 features, enable experimental support for decorators, and configure the target to ES6.
- Add a script to our package.json file to start the development server:
{
// ...
"type": "module",
"scripts": {
"start": "ts-node-dev main.ts"
},
// ...
}
Here, we are adding a start script that uses ts-node-dev to run the main.ts file, which will be our entry point for the application.
- Install the necessary dependencies for our application:
npm install express mariadb reflect-metadata sequelize sequelize-typescript --save
npm install @types/express @types/validator --save-dev
Here, we are installing the production dependencies for our application, including Express, MariaDB, Sequelize, and sequelize-typescript. Additionally, we are installing the development dependencies for TypeScript definitions for Express and validator.
Overall, these steps set up our Node.js project with TypeScript and provide us with the necessary dependencies to start building our API using Express.js and Sequelize.
Creating the API
In this section we will explore the process of building a RESTful API using Express.js and Sequelize. They offer a variety of features such as routing, middleware support, and error handling.
With the assistance of these frameworks. It is possible to construct an API that has the capability to execute CRUD operations on a database table. Let's get started.
// Import required dependencies
import "reflect-metadata";
import express, { Request, Response } from "express";
// Create a new express app
const app = express();
// Enable express to parse JSON data
app.use(express.json());
// Define a route for the root URL
app.get("/", (req: Request, res: Response): Response => {
return res.json({ message: "Sequelize Example 🤟" });
});
// Define a function to start the server
const start = async (): Promise<void> => {
try {
// Start listening on port 3000
app.listen(3000, () => {
console.log("Server started on port 3000");
});
} catch (error) {
// Log any errors and exit the process
console.error(error);
process.exit(1);
}
};
// Call the start function to start the server
void start();
The above code sets up an Express.js server that listens on port 3000. It defines a route for the root URL ("/") that returns a JSON response with a message. To initiate the server. The start function is called which not only logs a message on server commencement but also takes care of any encountered errors.
Initializing the database connection
// Import the Sequelize module from sequelize-typescript
import { Sequelize } from "sequelize-typescript";
// Import the Customer model from the ./models module
import { Customer } from "./models";
// Create a new Sequelize instance with the connection configuration
const connection = new Sequelize({
dialect: "mariadb", // Specifies the database dialect
host: "localhost", // Specifies the database host
username: "root", // Specifies the database username
password: "root", // Specifies the database password
database: "sequelize", // Specifies the database name
logging: false, // Disables logging of SQL queries
models: [Customer], // Associates the Customer model with this Sequelize instance
});
// Export the connection object as the default module
export default connection;
Here, we set up a connection to a MariaDB database using the Sequelize module. It imports the Sequelize class from sequelize-typescript and the Customer model from the ./models module.
A new instance of Sequelize is created with the specified connection configuration, including the database dialect, host, username, password, database name, and logging options. The models property is used to associate the Customer model with this Sequelize instance.
Finally, the connection object is exported as the default module, allowing other parts of the code to import and use this connection for database operations. You must now restart the server and sync the database.
Starting the server and syncing the database
// Import necessary modules
import "reflect-metadata";
import express, { Request, Response } from "express";
// Import the connection object from ./database
import connection from "./database";
// Create a new Express application
const app = express();
// ...
// Define an asynchronous function to start the server and sync the database
const start = async (): Promise<void> => {
try {
await connection.sync(); // Synchronizes the database with the defined models
app.listen(3000, () => { // Starts the server on port 3000
console.log("Server started on port 3000");
});
} catch (error) {
console.error(error); // Logs any errors that occur
process.exit(1); // Exits the process with an error status code
}
};
void start(); // Invokes the start function to start the server
This code sets up an Express.js server and establishes a connection to a database using the connection object imported from the ./database module.
With this, it creates a new Express application, defined as an asynchronous function named start, and starts the server on port 3000. Inside the start function, the connection.sync() method is called to synchronize the database with the defined models. If an error pops up, it gets logged on the console or exits with error status code.
The code concludes with a void start() statement which is responsible for invoking the start function to initialize the server and establish the database connection.
CRUD Operations
- Retrieving all Customers
/**
* GET all customers from the database
* @returns an array of all customers
*/
app.get("/customers", async (req: Request, res: Response): Promise<Response> => {
const allCustomers: Customer[] = await Customer.findAll();
return res.status(200).json(allCustomers);
});
In the above code, a route handler for the GET method on the “/customers” path is defined. It uses an async function to query the database for all records in the Customer table using the findAll method. It then returns a response with a status code of 200 (OK) and a JSON body containing an array of all customers.
- Retrieving a single customer by ID
// GET a single customer by id
app.get("/customers/:id", async (req: Request, res: Response): Promise<Response> => {
// Get the id parameter from the request URL
const { id } = req.params;
// Find the customer with the specified id using Sequelize's `findByPk()` method
const customer: Customer
Using an async function to get the id parameter from the request URL and find the customer with that id in the database using Sequelize findByPk() method.
Following the submission of the request, a response will be obtained. This response will have a status code of 200 (OK) if the customer object is found. The response will also contain the customer object in JSON format. If no customers are discovered, the status code is 404 (Not discovered), and an error message is provided in the response.
Conclusion
This blog provides a comprehensive guide to setting up and using Sequelize with TypeScript to build robust and maintainable APIs. We covered the fundamentals of using Sequelize, including creating models, defining associations, and managing database changes with migrations.
So get started with sequelize CLI, and integrate it with TypeScript to build scalable and maintainable APIs and manage complex database schemas.
Happy coding!
Posted on July 19, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.