Sequelize is a promise-based Node.js ORM that supports the dialects for Postgres, MySQL, SQL Server… Using an ORM can simplify database interactions, improve code maintainability, and speed up development by providing an object-oriented approach to working with relational databases. In this tutorial, I will show you step by step to implement CRUD operations using Typescript ORM with Postgres example.
You should install Postgres in your machine first.
Working with following front-end:
– Angular 8 / Angular 10 / Angular 11 / Angular 12 / Angular 13 / Angular 14 / Angular 15 / Angular 16
– Vue 2 Typescript / Vue 3 Typescript
– React Typescript Component / React Typescript Hooks
Related Posts:
– Express Typescript example
– Security: Node.js JWT Authentication with PostgreSQL example
Contents
Typescript
TypeScript is an open-source programming language developed and maintained by Microsoft. It is a superset of JavaScript, which means that any valid JavaScript code is also valid TypeScript code. However, TypeScript adds static typing and other features to JavaScript to enhance its development experience and help catch errors at compile-time rather than runtime.
Here are some key features and concepts of TypeScript:
- Static Typing: TypeScript introduces static typing, allowing you to explicitly declare the types of variables, function parameters, and return types. This helps catch type-related errors during development and provides better code documentation and editor support.
- Type Inference: TypeScript has a powerful type inference system that can automatically infer the types of variables based on their initial values. This reduces the need for explicit type annotations while still providing the benefits of static typing.
- Interfaces: TypeScript supports the definition of interfaces, which are used to define contracts for object structures. Interfaces specify the names and types of properties or methods that an object must have to conform to the interface.
- Classes: TypeScript introduces classes, allowing you to use object-oriented programming concepts such as inheritance, encapsulation, and polymorphism. Classes in TypeScript can have properties, methods, constructors, and support for access modifiers like public, private, and protected.
- Modules: TypeScript provides a module system that helps organize and encapsulate code into reusable units. Modules allow you to define public and private members and provide a way to structure larger applications.
- Generics: TypeScript supports generics, which enable the creation of reusable components that can work with different types. Generics allow you to write code that is more flexible and type-safe by parameterizing types and functions.
- Decorators: TypeScript supports decorators, which are a way to add metadata or modify the behavior of classes, methods, or properties at design time. Decorators are heavily used in frameworks like Angular for features like dependency injection, component declaration, and more.
- Tooling and Integration: TypeScript integrates well with modern development tools and workflows. It provides excellent editor support with features like autocompletion, type checking, and refactoring. TypeScript code is transpiled to JavaScript, allowing it to run in any JavaScript environment.
ORM
ORM stands for Object-Relational Mapping. It is a programming technique and a software design pattern that allows developers to interact with relational databases using object-oriented programming languages. The main goal of an ORM is to bridge the gap between the relational database and the programming language, providing a high-level abstraction layer to work with database entities as objects.
Here are some key concepts and benefits of using an ORM:
- Object-Relational Mapping: ORM frameworks provide mechanisms to map database tables and columns to corresponding objects and attributes in the programming language. This allows developers to interact with the database using familiar object-oriented concepts, such as classes, objects, and methods.
- Abstraction of Database Queries: With an ORM, developers can perform database operations (such as creating, reading, updating, and deleting records) using high-level methods and APIs provided by the ORM framework. This abstracts away the need to write low-level SQL queries manually, simplifying the code and making it more maintainable.
- Automatic Query Generation: ORMs can generate SQL queries dynamically based on the defined object mappings and the operations performed on the objects. This eliminates the need for developers to write complex SQL queries manually, reducing the risk of errors and saving development time.
- Data Validation and Type Safety: ORM frameworks often provide built-in mechanisms for data validation, ensuring that the data being stored or retrieved from the database adheres to specified rules and constraints. Additionally, ORMs enforce type safety by mapping database columns to specific data types in the programming language.
- Database Independence: ORMs can abstract away the specific database implementation details, allowing developers to write database-agnostic code. This makes it easier to switch between different database systems without having to rewrite the entire data access layer.
- Relationship Management: ORMs typically provide features to handle relationships between database tables/entities, such as one-to-one, one-to-many, and many-to-many relationships. These relationships can be defined through object associations or annotations, and the ORM handles the necessary database operations transparently.
- Performance Optimization: Many ORM frameworks offer features like lazy loading and caching to optimize performance. Lazy loading delays the loading of related data until it is explicitly accessed, reducing unnecessary database queries. Caching allows frequently accessed data to be stored in memory, improving performance by reducing database round trips.
Some popular ORM frameworks in the JavaScript ecosystem:
– Sequelize: A widely used ORM for Node.js and JavaScript, supporting various databases like PostgreSQL, MySQL, SQLite, and more.
– TypeORM: An ORM for TypeScript and JavaScript that supports multiple databases, including PostgreSQL, MySQL, SQLite, and MongoDB.
– Prisma: A modern database toolkit and ORM that provides a type-safe and auto-generated query builder for Node.js and TypeScript.
In this tutorial, we will use sequelize-typescript as our ORM.
Import Typescript ORM and Postgres
We need to install necessary modules: sequelize
, sequelize-typescript
, typescript
, ts-node
, @types/node
and pg
.
Run the command:
npm install typescript ts-node @types/node --save-dev
npm install sequelize sequelize-typescript pg
The tsconfig.json file needs the following flags:
{
"compilerOptions": {
...
"target": "es6", // or a more recent ecmascript version
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
...
}
Configure Postgres database
In the src folder, we create a separate config folder for configuration with db.config.ts file like this:
export const config = {
HOST: "localhost",
USER: "postgres",
PASSWORD: "123",
DB: "testdb",
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
};
export const dialect = "postgres";
First five parameters are for PostgreSQL connection.
pool
is optional, it will be used for Sequelize connection pool configuration:
max
: maximum number of connection in poolmin
: minimum number of connection in poolidle
: maximum time, in milliseconds, that a connection can be idle before being releasedacquire
: maximum time, in milliseconds, that pool will try to get connection before throwing error
For more details, please visit API Reference for the Sequelize constructor.
Initialize Database and ORM
Using the configuration above, we’re gonna initialize Sequelize ORM with the following code:
src/db/index.ts
import { Sequelize } from "sequelize-typescript";
import { config, dialect } from "../config/db.config";
import Tutorial from "../models/tutorial.model";
class Database {
public sequelize: Sequelize | undefined;
constructor() {
this.connectToDatabase();
}
private async connectToDatabase() {
this.sequelize = new Sequelize({
database: config.DB,
username: config.USER,
password: config.PASSWORD,
host: config.HOST,
dialect: dialect,
pool: {
max: config.pool.max,
min: config.pool.min,
acquire: config.pool.acquire,
idle: config.pool.idle
},
models: [Tutorial]
});
await this.sequelize
.authenticate()
.then(() => {
console.log("Connection has been established successfully.");
})
.catch((err) => {
console.error("Unable to connect to the Database:", err);
});
}
}
export default Database;
Additional to Sequelize database configuration fields, we also need to specify path to models
, which should be loaded for Sequelize instance:
new Sequelize({ ..., models: [Tutorial]});
Define the ORM Model
In models folder, create tutorial.model.ts file like this:
import { Model, Table, Column, DataType } from "sequelize-typescript";
@Table({
tableName: "tutorials",
})
export default class Tutorial extends Model {
@Column({
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true,
field: "id"
})
id?: number;
@Column({
type: DataType.STRING(255),
field: "title"
})
title?: string;
@Column({
type: DataType.STRING(255),
field: "description"
})
description?: string;
@Column({
type: DataType.BOOLEAN,
field: "published"
})
published?: boolean;
}
This Sequelize Model represents tutorials table in Postgres database. These columns will be generated automatically: id, title, description, published (and createdAt, updatedAt by default).
After initializing Sequelize, we don’t need to write CRUD functions, Sequelize supports all of them:
- create a new Tutorial:
create(object)
- find a Tutorial by id:
findByPk(id)
- get all Tutorials:
findAll()
- update a Tutorial by id:
update(data, where: { id: id })
- remove a Tutorial:
destroy(where: { id: id })
- remove all Tutorials:
destroy(where: {})
- find all Tutorials by title:
findAll({ where: { title: ... } })
These functions will be used in our Repository.
Create the Repository
Inside srx/repositories folder, let’s create tutorial.repository.ts with these CRUD functions:
- save
- retrieveAll
- retrieveById
- update
- delete
- deleteAll
import Tutorial from "../models/tutorial.model";
interface ITutorialRepository {
save(tutorial: Tutorial): Promise<Tutorial>;
retrieveAll(searchParams: {title: string, published: boolean}): Promise<Tutorial[]>;
retrieveById(tutorialId: number): Promise<Tutorial | null>;
update(tutorial: Tutorial): Promise<number>;
delete(tutorialId: number): Promise<number>;
deleteAll(): Promise<number>;
}
class TutorialRepository implements ITutorialRepository {
async save(tutorial: Tutorial): Promise<Tutorial> { }
async retrieveAll(searchParams: {title?: string, published?: boolean}): Promise<Tutorial[]> { }
async retrieveById(tutorialId: number): Promise<Tutorial | null> { }
async update(tutorial: Tutorial): Promise<number> { }
async delete(tutorialId: number): Promise<number> { }
async deleteAll(): Promise<number> { }
}
export default new TutorialRepository();
Let’s implement each CRUD operation.
Create new object
async save(tutorial: Tutorial): Promise<Tutorial> {
try {
return await Tutorial.create({
title: tutorial.title,
description: tutorial.description,
published: tutorial.published
});
} catch (err) {
throw new Error("Failed to create Tutorial!");
}
}
Retrieve objects (with conditions)
import { Op } from "sequelize";
async retrieveAll(searchParams: {title?: string, published?: boolean}): Promise<Tutorial[]> {
try {
let condition: SearchCondition = {};
if (searchParams?.published) condition.published = true;
if (searchParams?.title)
condition.title = { [Op.like]: `%${searchParams.title}%` };
return await Tutorial.findAll({ where: condition });
} catch (error) {
throw new Error("Failed to retrieve Tutorials!");
}
}
Retrieve object by Id
async retrieveById(tutorialId: number): Promise<Tutorial | null> {
try {
return await Tutorial.findByPk(tutorialId);
} catch (error) {
throw new Error("Failed to retrieve Tutorials!");
}
}
Update an object
async update(tutorial: Tutorial): Promise<number> {
const { id, title, description, published } = tutorial;
try {
const affectedRows = await Tutorial.update(
{ title, description, published },
{ where: { id: id } }
);
return affectedRows[0];
} catch (error) {
throw new Error("Failed to update Tutorial!");
}
}
Delete an object
async delete(tutorialId: number): Promise<number> {
try {
const affectedRows = await Tutorial.destroy({ where: { id: tutorialId } });
return affectedRows;
} catch (error) {
throw new Error("Failed to delete Tutorial!");
}
}
Delete all objects
async deleteAll(): Promise<number> {
try {
return Tutorial.destroy({
where: {},
truncate: false
});
} catch (error) {
throw new Error("Failed to delete Tutorials!");
}
}
Create the Controller and Routes
Inside src/controllers folder, we create tutorial.controller.ts with these CRUD functions:
- create
- findAll
- findOne
- update
- delete
- deleteAll
- findAllPublished
When a client sends request for an endpoint using HTTP request (GET, POST, PUT, DELETE), we need to determine how the server will response by setting up the routes.
These are our routes:
/api/tutorials
: GET, POST, DELETE/api/tutorials/:id
: GET, PUT, DELETE/api/tutorials/published
: GET
Inside src/routes folder, create index.ts file.
import { Application } from "express";
import tutorialRoutes from "./tutorial.routes";
export default class Routes {
constructor(app: Application) {
app.use("/api/tutorials", tutorialRoutes);
}
}
We import TutorialRoutes
for handling HTTP Requests with /api/tutorials
endpoint.
The tutorial.routes.ts will look like this:
So this is our project structure:
For more details about implementing the Controller and Routes, kindly visit:
Express Typescript example
Run and Check
Run the Node.js Typescript Postgres Rest APIs with command:
npm run start
The console shows:
Server is running on port 8080.
Executing (default): SELECT 1+1 AS result
Executing (default): SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'tutorials'
Connection has been established successfully.
Executing (default): CREATE TABLE IF NOT EXISTS "tutorials" ("id" SERIAL , "title" VARCHAR(255), "description" VARCHAR(255), "published" BOOLEAN, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY ("id"));
Executing (default): SELECT i.relname AS name, ix.indisprimary AS primary, ix.indisunique AS unique, ix.indkey AS indkey, array_agg(a.attnum) as column_indexes, array_agg(a.attname) AS column_names, pg_get_indexdef(ix.indexrelid) AS definition FROM pg_class t, pg_class i, pg_index ix, pg_attribute a WHERE t.oid = ix.indrelid AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND t.relkind = 'r' and t.relname = 'tutorials' GROUP BY i.relname, ix.indexrelid, ix.indisprimary, ix.indisunique, ix.indkey ORDER BY i.relname;
Using Postman, we’re gonna test all the APIs above.
– Create a new Tutorial: POST http://localhost:8080/api/tutorials
After creating some new Tutorials, you can check Postgres table:
– Retrieve all Tutorials: GET http://localhost:8080/api/tutorials
– Retrieve a single Tutorial by id: GET http://localhost:8080/api/tutorials/[id]
– Update a Tutorial: PUT http://localhost:8080/api/tutorials/[id]
Check tutorials
table after some rows were updated:
– Find all Tutorials which title contains ‘postgres’: GET http://localhost:8080/api/tutorials?title=postgres
– Find all published Tutorials: GET http://localhost:8080/api/tutorials/published
– Delete a Tutorial: DELETE http://localhost:8080/api/tutorials/[id]
Tutorial with id=4 was removed from tutorials
table.
– Delete all Tutorials: DELETE http://localhost:8080/api/tutorials
Now there are no rows in tutorials
table.
You can use the Simple HTTP Client using Axios to check it.
Or: Simple HTTP Client using Fetch API
Conclusion
Today, we’ve learned how to implement Typescript ORM with Postgres database and Sequelize. We also know way to add configuration for Postgres database & Sequelize, create a Sequelize Model, write a controller and define routes for handling all CRUD operations in a Node.js Rest API with an Express web server.
You can find more interesting thing in the next tutorial:
– Node.js Express Pagination with PostgreSQL example
Return pagination data in response:
{
"totalItems": 8,
"tutorials": [...],
"totalPages": 3,
"currentPage": 1
}
Happy learning! See you again.
Further Reading
- Express.js Routing
- https://www.npmjs.com/package/express
- https://www.npmjs.com/package/pg
- Tutorials and Guides for Sequelize
Fullstack CRUD Application:
– Vue.js + Node.js + Express + PostgreSQL example
– Angular 8 + Node.js Express + PostgreSQL example
– Angular 10 + Node.js Express + PostgreSQL example
– Angular 11 + Node.js Express + PostgreSQL example
– Angular 12 + Node.js Express + PostgreSQL example
– Angular 13 + Node.js Express + PostgreSQL example
– Angular 14 + Node.js Express + PostgreSQL example
– Angular 15 + Node.js Express + PostgreSQL example
– Angular 16 + Node.js Express + PostgreSQL example
– React + Node.js + Express + PostgreSQL example
File Upload Rest API:
– Node.js Express File Upload Rest API example using Multer
– Google Cloud Storage with Node.js: File Upload example
Source code
You can find the complete source code for this example on Github.
If you want to add Comments for each Tutorial. It is the One-to-Many Association, there is a tutorial for that Relationship: Sequelize Associations: One-to-Many example
Or you can add Tags for each Tutorial and add Tutorials to Tag (Many-to-Many Relationship):
Sequelize Many-to-Many Association example