- Vamos iniciar gerando a migration para criar a tabela favorites:
npx sequelize-cli migration:generate --name create-favorites-table
- Na migration adicione a criação da tabela e suas colunas:
// src/database/migration/XXXXXXXXXXXXXX-create-favorites-table.js
'use strict';
module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.createTable('favorites', {
user_id: {
allowNull: false,
type: Sequelize.DataTypes.INTEGER,
references: { model: 'users', key: 'id' },
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
course_id: {
allowNull: false,
type: Sequelize.DataTypes.INTEGER,
references: { model: 'courses', key: 'id' },
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
created_at: {
allowNull: false,
type: Sequelize.DATE
},
updated_at: {
allowNull: false,
type: Sequelize.DATE
}
})
},
async down (queryInterface, Sequelize) {
await queryInterface.dropTable('favorites')
}
};
- Com a migration pronta, basta rodar as migrations:
npx sequelize-cli db:migrate
- Agora precisamos criar o modelo. Por se tratar de uma tabela para uma relação m-m poderíamos pular a criação do model e suas associações e o sequelize criaria automaticamente uma forma de trabalharmos com essa tabela. No entanto, ao criarmos o modelo e adicionarmos algumas associações o sequelize permitirá mais flexibilidade na hora de trabalhar com a tabela favorites:
- Obs.: Repare que vamos incluir as propriedades course e user nas nossas instâncias de Favorite. Elas serão necessárias aqui pois quando carregarmos as informações do curso com eager loading o typescript saberá que uma propriedade course existe (ela armazenará os dados carregados do curso via eager loading).
- Obs².: As propriedades e métodos referentes às associações do sequelize precisam ser inseridas manualmente na interface da instância como estamos fazendo agora. Elas não ficam disponíveis automaticamente, se não inserirmos o typescript não saberá que elas existem.
// src/models/Favorite.ts
import { DataTypes, Model } from "sequelize"
import { sequelize } from "../database"
import { CourseInstance } from "./Course"
import { UserInstance } from "./User"
export interface Favorite {
userId: number
courseId: number
}
export interface FavoriteInstance extends Model<Favorite>, Favorite {
Course?: CourseInstance
User?: UserInstance
}
export const Favorite = sequelize.define<FavoriteInstance, Favorite>('Favorite', {
userId: {
allowNull: false,
primaryKey: true,
type: DataTypes.INTEGER,
references: {
model: 'users',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
},
courseId: {
allowNull: false,
primaryKey: true,
type: DataTypes.INTEGER,
references: {
model: 'courses',
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'CASCADE'
}
})
- Agora vamos incluir as associações do modelo:
// src/models/index.ts
import { Category } from './Category'
import { Course } from './Course'
import { Episode } from './Episode'
import { Favorite } from './Favorite'
import { User } from './User'
Category.hasMany(Course)
Course.belongsTo(Category)
Course.hasMany(Episode)
Course.belongsToMany(User, { through: Favorite })
Course.hasMany(Favorite, { as: 'favoritesUsers', foreignKey: 'course_id' })
Episode.belongsTo(Course)
Favorite.belongsTo(Course)
Favorite.belongsTo(User)
User.belongsToMany(Course, { through: Favorite })
User.hasMany(Favorite, { as: 'favoritesCourses', foreignKey: 'user_id' })
export {
Category,
Course,
Episode,
Favorite,
User
}
- Com o modelo pronto, podemos criar uma rota para adicionar um novo registro ao banco de dados. Para isso vamos começar pelo serviço. Crie o arquivo favoriteService.ts:
// src/services/favoriteService.ts
import { Favorite } from "../models/favorite"
export const favoriteService = {
create: async (userId: number, courseId: number) => {
const favorite = await Favorite.create({
userId,
courseId
})
return favorite
},
}
- Com o serviço pronto, podemos criar um controlador:
- Obs.: Repare como estamos utilizando o tipo RequestWithUser no método ao invés de apenas Request. Isso porque precisamos que o typescript saiba que essa nossa requisição possui o user que definimos no middleware. Outros métodos de controller também possuem o user, mas como não trabalhamos diretamente com ele não precisamos deixar explícito para o typescript.
- Obs².: Outro detalhe importante é o uso do “!”. No typescript o ponto de exclamação é usado para garantir (de forma forçada) que uma propriedade não é nula. Isso deve ser feito com cautela, mas nesse caso temos certeza que a requisição terá essa propriedade pois o middleware de autenticação já terá sido executado a essa altura.
// src/controllers/favoritesController.ts
import { Response } from 'express'
import { AuthenticatedRequest } from "../middlewares/auth";
import { favoriteService } from '../services/favoriteService'
export const favoritesController = {
// POST /favorites
save: async (req: AuthenticatedRequest, res: Response) => {
const userId = req.user!.id
const { courseId } = req.body
try {
const favorite = await favoriteService.create(userId, courseId)
return res.status(201).json(favorite)
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({ message: err.message })
}
}
}
}
- Por fim, só precisamos criar uma rota e incluir o método do controlador:
// src/routes.ts
// ...
import { episodesController } from './controllers/episodesController'
import { favoritesController } from './controllers/favoritesController'
// ...
router.post('/favorites', ensureAuth, favoritesController.save)
export { router }