DEV Community

Cover image for Build Type Safe API with Sequelize TypeScript and Express.js
Francisco Mendes
Francisco Mendes

Posted on • Updated on

Build Type Safe API with Sequelize TypeScript and Express.js

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
Enter fullscreen mode Exit fullscreen mode

Next, initialize a TypeScript project and add the necessary dependencies:

npm init -y
npm install typescript ts-node-dev @types/node --save-dev
Enter fullscreen mode Exit fullscreen mode

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",
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's add the following script to our package.json file.

{
  // ...
  "type": "module",
  "scripts": {
    "start": "ts-node-dev main.ts"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

For the API to be initialized on port 3000 just run the following command:

npm start
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
  }
);
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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! šŸ§¦

Discussion (2)

Collapse
mariastommes profile image
Maria Stommes • Edited on

Worked great! Thanks for the nice tutorial. The only missing for some may be a mention of starting a local db instance and creating the associated tables.

Collapse
franciscomendes10866 profile image
Francisco Mendes Author

Thanks for the feedback! šŸ’Ŗ In the future I will take this into account. šŸ‘Š