Skip to content

Sequelize Typescript API REST con Express

En este tutorial aprenderemos a trabajar con Sequelize ORM en un proyecto de Node.js con Express y TypeScript. Construiremos una API REST que nos permitirá interactuar con una base de datos SQLite utilizando Sequelize como ORM. A lo largo de la guía, veremos cómo configurar Sequelize en nuestro proyecto, definir los modelos de la base de datos, realizar consultas a la base de datos utilizando Sequelize y Express, y validar los datos de entrada en las rutas de nuestra API.

Extensiones VSCode (opcional):

  1. Material Icon Theme Personaliza los íconos de tus archivos y carpetas con este tema de íconos visualmente atractivo. Ideal para mejorar la navegación y organización de proyectos.
  2. Moonlight Un tema oscuro inspirado en colores suaves y relajantes, perfecto para sesiones de programación prolongadas.
  3. Pretty TypeScript Errors Transforma los errores de TypeScript en mensajes más claros y fáciles de entender, optimizando el proceso de depuración.
  4. SQLite Viewer Una herramienta para explorar bases de datos SQLite directamente desde Visual Studio Code, con una interfaz fácil de usar.
  5. Thunder Client Una alternativa ligera y rápida a Postman, diseñada para realizar pruebas de API directamente en el editor.

Configuración del proyecto

sh
npm init -y
sh
npm i
express
reflect-metadata
sequelize
sequelize-typescript
sqlite3
sh
npm i -D
@types/express
@types/node
@types/validator
tsx
typescript
sh
npx tsc --init

package.json

json
"scripts": {
  "dev": "tsx --watch src/server.ts"
}
sh
npm run dev

Sequelize Typescript

src/database/sequelize.ts

ts
import { Sequelize } from "sequelize-typescript";
import { Post, User } from "./schema";

export const sequelize = new Sequelize({
  dialect: "sqlite",
  storage: "database.sqlite",
  models: [User, Post],
});

// sequelize.sync({ force: true });

src/database/schema.ts

ts
import {
  AllowNull,
  BelongsTo,
  Column,
  DataType,
  Default,
  ForeignKey,
  HasMany,
  IsEmail,
  IsUUID,
  Length,
  Model,
  PrimaryKey,
  Table,
  Unique,
} from "sequelize-typescript";

@Table
export class User extends Model {
  @IsUUID(4)
  @PrimaryKey
  @Default(DataType.UUIDV4)
  @Column(DataType.UUID)
  declare id: string;

  @AllowNull(false)
  @IsEmail
  @Unique
  @Column(DataType.STRING)
  email!: string;

  @AllowNull(false)
  @Length({ min: 2, max: 255 })
  @Column(DataType.STRING)
  name!: string;

  @HasMany(() => Post)
  posts!: Post[];
}

@Table
export class Post extends Model {
  @IsUUID(4)
  @PrimaryKey
  @Default(DataType.UUIDV4)
  @Column(DataType.UUID)
  declare id: string;

  @AllowNull(false)
  @Length({ min: 2, max: 255 })
  @Column(DataType.STRING)
  title!: string;

  @AllowNull(false)
  @Length({ min: 2 })
  @Column(DataType.TEXT)
  content!: string;

  @Default(false)
  @Column(DataType.BOOLEAN)
  published!: boolean;

  @ForeignKey(() => User)
  @AllowNull(false)
  @Column(DataType.UUID)
  userId!: string;

  @BelongsTo(() => User)
  author!: User;
}

server.ts

ts
import express from "express";
import { sequelize } from "./database/sequelize";

import postRoute from "./routes/post.route";
import userRoute from "./routes/user.route";

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.use("/api/v1/users", userRoute);
app.use("/api/v1/posts", postRoute);

const main = async () => {
  try {
    // await sequelize.sync(); // Sincroniza la base de datos
    // await sequelize.sync({ force: true }); // Sincroniza la base de datos y elimina los datos existentes
    await sequelize.sync({ alter: true }); // Sincroniza la base de datos y modifica las tablas existentes
    console.log("Connection has been established successfully.");
    app.listen(PORT, () => {
      console.log(`Server is running on http://localhost:${PORT}`);
    });
  } catch (error) {
    console.error("Unable to connect to the database:", error);
  }
};
main();

src/routes/user.route.ts

ts
import { Router } from "express";
import { ValidationError } from "sequelize";
import validator from "validator";
import { Post, User } from "../database/schema";

const router = Router();

router.get("/", async (req, res) => {
  try {
    const users = await User.findAll();
    return void res.json(users);
  } catch (error) {
    console.log(error);
    return void res.status(500).json({ message: "Internal server error" });
  }
});

// example user with posts
router.get("/:id", async (req, res) => {
  try {
    const { id } = req.params;
    const user = await User.findByPk(id);

    if (!user) {
      return void res.status(404).json({ message: "User not found" });
    }

    const userWithPosts = await Post.findAll({
      where: { userId: user.id },
    });

    return void res.json(userWithPosts);
  } catch (error) {
    console.log(error);
    return void res.status(500).json({ message: "Internal server error" });
  }
});

router.post("/", async (req, res) => {
  try {
    const { email, name } = req.body;

    if (!email || !validator.isEmail(email.trim())) {
      return void res.status(400).json({ message: "Invalid email address" });
    }

    const user = await User.create({ email, name });
    return void res.status(201).json(user);
  } catch (error) {
    console.log(error);
    if (error instanceof ValidationError) {
      return void res.status(400).json({
        message: "Validation Error",
        errors: error.errors.map((err) => err.message),
      });
    }
    return void res.status(500).json({ message: "Internal server error" });
  }
});

export default router;

src/routes/post.route.ts

ts
import { Router } from "express";
import { Error, ValidationError } from "sequelize";
import { Post } from "../database/schema";

const router = Router();

router.get("/", async (req, res) => {
  try {
    const posts = await Post.findAll();
    return void res.json(posts);
  } catch (error) {
    console.log(error);
    return void res.status(500).json({ message: "Internal server error" });
  }
});

router.post("/", async (req, res) => {
  try {
    const { title, content, userId } = req.body;

    const post = await Post.create({ title, content, userId });
    return void res.status(201).json(post);
  } catch (error) {
    console.log(error);

    if (error instanceof ValidationError) {
      return void res.status(400).json({
        message: "Validation Error",
        errors: error.errors.map((err) => err.message),
      });
    }

    if (
      error instanceof Error &&
      error.name === "SequelizeForeignKeyConstraintError"
    ) {
      return void res.status(400).json({ message: "User not found" });
    }

    return void res.status(500).json({ message: "Internal server error" });
  }
});

export default router;