Build Type Safe API with Sequelize TypeScript and Express.js
Francisco Mendes
Posted on December 17, 2021
Overview
I think Sequelize is the most popular ORM in the Node universe. However, it is widely used by the JavaScript community and not so much by the TypeScript community, because nowadays there are several alternatives with better support and development experience.
But in my opinion all that changes with the existence of the sequelize-typescript dependency. The way the entities are structured and the way the connection to the database is made becomes much more intuitive. This experience is ideal because you can transition from JavaScript to TypeScript in a much more convenient way and without changing the stack.
Today's example
In today's example we are going to set up a Node project with TypeScript. Then we will create an API using the Express.js framework and create the CRUD of our application.
Project setup
As a first step, create a project directory and navigate into it:
mkdir ts-sequelize
cd ts-sequelize
Next, initialize a TypeScript project and add the necessary dependencies:
npm init -y
npm install typescript ts-node-dev @types/node --save-dev
Next, create a tsconfig.json
file and add the following configuration to it:
{
"compilerOptions": {
"sourceMap": true,
"outDir": "dist",
"strict": true,
"lib": [
"esnext"
],
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "esnext",
"moduleResolution": "Node",
}
}
Now let's add the following script to our package.json
file.
{
// ...
"type": "module",
"scripts": {
"start": "ts-node-dev main.ts"
},
// ...
}
Now proceed with the installation of the Express and Sequelize dependencies (as well as their development dependencies):
npm install express mariadb reflect-metadata sequelize sequelize-typescript --save
npm install @types/express @types/validator --save-dev
Let's code
And now let's create a simple API:
// @/main.ts
import "reflect-metadata";
import express, { Request, Response } from "express";
const app = express();
app.use(express.json());
app.get("/", (req: Request, res: Response): Response => {
return res.json({ message: "Sequelize Example 🤟" });
});
const start = async (): Promise<void> => {
try {
app.listen(3000, () => {
console.log("Server started on port 3000");
});
} catch (error) {
console.error(error);
process.exit(1);
}
};
void start();
For the API to be initialized on port 3000
just run the following command:
npm start
Now we can start by creating our entity from today's example, as I've been doing lately, let's create a model called Dog that will have some fields such as our four legged friend's name, his race, age and if he has been a good boy or not. The template might look like this:
// @/models.ts
import { Table, Model, Column, DataType } from "sequelize-typescript";
@Table({
timestamps: false,
tableName: "dogs",
})
export class Dog extends Model {
@Column({
type: DataType.STRING,
allowNull: false,
})
name!: string;
@Column({
type: DataType.STRING,
allowNull: false,
})
breed!: string;
@Column({
type: DataType.BOOLEAN,
allowNull: true,
defaultValue: true,
})
isGoodBoy!: boolean;
}
Now with our defined model we can start working on configuring our database connection.
// @/connection.ts
import { Sequelize } from "sequelize-typescript";
import { Dog } from "./models";
const connection = new Sequelize({
dialect: "mariadb",
host: "localhost",
username: "root",
password: "root",
database: "sequelize",
logging: false,
models: [Dog],
});
export default connection;
With our template defined and our connection configured, it's now enough to go to main.ts
to initialize the connection when the node instance starts.
// @/main.ts
import "reflect-metadata";
import express, { Request, Response } from "express";
import connection from "./database";
const app = express();
// ...
const start = async (): Promise<void> => {
try {
await connection.sync();
app.listen(3000, () => {
console.log("Server started on port 3000");
});
} catch (error) {
console.error(error);
process.exit(1);
}
};
void start();
Now that we have everything ready, we can start working on our application's CRUD. First of all, let's create a route to get all the dogs we have in our database.
app.get("/dogs", async (req: Request, res: Response): Promise<Response> => {
const allDogs: Dog[] = await Dog.findAll();
return res.status(200).json(allDogs);
});
Next, let's just look for a dog through the id that is sent in the request parameters.
app.get("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
const dog: Dog | null = await Dog.findByPk(id);
return res.status(200).json(dog);
});
Now we need to insert a new record into our database table. For this, we will send the data from the request body.
app.post("/dogs", async (req: Request, res: Response): Promise<Response> => {
const dog: Dog = await Dog.create({ ...req.body });
return res.status(201).json(dog);
});
Next we need to update a record. For this, we will perform the update through the id and we will update the fields of the respective properties that are sent in the request body.
app.put("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
await Dog.update({ ...req.body }, { where: { id } });
const updatedDog: Dog | null = await Dog.findByPk(id);
return res.status(200).json(updatedDog);
});
Now that we can fetch all records, create a new record and update a specific record. We still need to remove a specific record from our database table. Similar to other routes in our API, let's do it via id.
app.delete("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
const deletedDog: Dog | null = await Dog.findByPk(id);
await Dog.destroy({ where: { id } });
return res.status(200).json(deletedDog);
}
);
As you may have noticed at the endpoints for updating and removing records from the database, the data of the updated/deleted element is returned in the response body just so that they have some kind of feedback on the action taken.
The final code of our main.ts
is as follows:
// @/main.ts
import "reflect-metadata";
import express, { Request, Response } from "express";
import connection from "./database";
import { Dog } from "./models";
const app = express();
app.use(express.json());
app.get("/dogs", async (req: Request, res: Response): Promise<Response> => {
const allDogs: Dog[] = await Dog.findAll();
return res.status(200).json(allDogs);
});
app.get("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
const dog: Dog | null = await Dog.findByPk(id);
return res.status(200).json(dog);
});
app.post("/dogs", async (req: Request, res: Response): Promise<Response> => {
const dog: Dog = await Dog.create({ ...req.body });
return res.status(201).json(dog);
});
app.put("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
await Dog.update({ ...req.body }, { where: { id } });
const updatedDog: Dog | null = await Dog.findByPk(id);
return res.status(200).json(updatedDog);
});
app.delete("/dogs/:id", async (req: Request, res: Response): Promise<Response> => {
const { id } = req.params;
const deletedDog: Dog | null = await Dog.findByPk(id);
await Dog.destroy({ where: { id } });
return res.status(200).json(deletedDog);
}
);
const start = async (): Promise<void> => {
try {
await connection.sync();
app.listen(3000, () => {
console.log("Server started on port 3000");
});
} catch (error) {
console.error(error);
process.exit(1);
}
};
void start();
Conclusion
As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻💻
Hope you have a great day! 🧦
Posted on December 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.