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.
Links
Extensiones VSCode (opcional):
- 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.
- Moonlight Un tema oscuro inspirado en colores suaves y relajantes, perfecto para sesiones de programación prolongadas.
- Pretty TypeScript Errors Transforma los errores de TypeScript en mensajes más claros y fáciles de entender, optimizando el proceso de depuración.
- SQLite Viewer Una herramienta para explorar bases de datos SQLite directamente desde Visual Studio Code, con una interfaz fácil de usar.
- 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;