Me carga el payaso… tengo más de 20 migraciones porque me puse a jugar con las tablas y las columnas en clases anteriores. Me tocó hacer una faena épica.
Introducción
Persistencia de datos en Node.js
Platzi Store: instalación y presentación del proyecto
Base de datos
Instalación de Docker
Configuración de Postgres en Docker
Explorando Postgres: interfaces gráficas vs. terminal
Integración de node-postgres
Manejando un Pool de conexiones
Variables de ambiente en Node.js
Sequelize
¿Qué es un ORM? Instalación y configuración de Sequelize ORM
Tu primer modelo en Sequelize
Crear, actualizar y eliminar
Cambiando la base de datos a MySQL
Migraciones
¿Qué son las migraciones? Migraciones en Sequelize ORM
Configurando y corriendo migraciones con npm scripts
Modificando una entidad
Relaciones
Relaciones uno a uno
Resolviendo las relaciones uno a uno
Relaciones uno a muchos
Resolviendo relaciones uno a muchos
Consultas
Órdenes de compra
Relaciones muchos a muchos
Resolviendo relaciones muchos a muchos
Paginación
Filtrando precios con operadores
Despliegue
Deploy en Heroku
Consideraciones al hacer migraciones
Próximos pasos
Toma con el Curso de Backend con Node.js: Autenticación con Passport.js y JWT
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Aportes 25
Preguntas 14
Me carga el payaso… tengo más de 20 migraciones porque me puse a jugar con las tablas y las columnas en clases anteriores. Me tocó hacer una faena épica.
Mi archivo único de migraciones es algo como esto:
'use strict';
const { DataTypes, Sequelize } = require('sequelize');
const { USER_TABLE, UserSchema } = require('../models/userModel')
const { CUSTOMER_TABLE, CustomerSchema } = require('../models/customerModel')
const { CATEGORY_TABLE, CategorySchema } = require('../models/categoryModel')
const { PRODUCT_TABLE, ProductSchema } = require('../models/productModel')
const { ORDER_TABLE } = require('../models/orderModel')
const { OrderProductSchema, ORDER_PRODUCT_TABLE } = require('../models/order-productModel')
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(USER_TABLE, UserSchema);
await queryInterface.createTable(CUSTOMER_TABLE, CustomerSchema);
await queryInterface.createTable(CATEGORY_TABLE, CategorySchema);
await queryInterface.createTable(PRODUCT_TABLE, ProductSchema);
await queryInterface.createTable(ORDER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
customerId: {
field: 'customer_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CUSTOMER_TABLE,
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'created_at',
defaultValue: Sequelize.NOW,
},
});
await queryInterface.createTable(ORDER_PRODUCT_TABLE, OrderProductSchema);
},
down: async (queryInterface) => {
await queryInterface.dropTable(USER_TABLE)
await queryInterface.dropTable(CUSTOMER_TABLE)
await queryInterface.dropTable(CATEGORY_TABLE)
await queryInterface.dropTable(PRODUCT_TABLE)
await queryInterface.dropTable(ORDER_TABLE)
await queryInterface.dropTable(ORDER_PRODUCT_TABLE)
}
};
Grande Nicolás! Te la comiste con este curso. Mil gracias, aprendí un montón!
este curso está 10/10 ☄
En mi opinión (para este caso) es mucho más sencillo borrar todas las migraciones y crear una sola en limpio con su respectivo código.
Con esto no habría ningún inconveniente al subirlo a producción. Claro, habría otras complicaciones más adelante en Development, pero si no tienen problema, pueden borrar y crear nuevamente las tablas de la base de datos.
Las migraciones son muy delicadas, es por ello que se deben tener en cuenta los esquemas. A nivel producción no se recomienda crear migraciones paso a paso, más bien, crear una sola migración que ya tenga todas las relaciones y configuración por defecto.
Una buena practica es no repetir el código, pero en este caso se va necesitar repetir el esquema cuando se creen las tablas.
Cada vez que agregamos un atributo o algo parecido, lo mejor es no basarse en el esquema porque es algo genérico y no está versionado, sirve para el modelo, pero no es buena practica que sea la base de las migraciones porque lo configuramos en cualquier momento y puede dañar la configuración.
Los archivos que se cambiaron fueron los siguientes:
Migración create-user.js
:
'use strict';
const { USER_TABLE } = require('../models/user.model');
const { DataTypes, Sequelize } = require('sequelize');
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(USER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
password: {
allowNull: false,
type: DataTypes.STRING,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'created_at',
defaultValue: Sequelize.NOW,
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable(USER_TABLE);
},
};
Migración add-role.js
:
'use strict';
const { USER_TABLE } = require('../models/user.model');
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface) => {
await queryInterface.addColumn(USER_TABLE, 'role', {
allowNull: false,
type: DataTypes.STRING,
defaultValue: 'customer',
});
},
down: async (queryInterface) => {
await queryInterface.removeColumn(USER_TABLE, 'role');
},
};
Migración create-customers.js
:
'use strict';
const { CUSTOMER_TABLE } = require('../models/customer.model');
const { USER_TABLE } = require('../models/user.model');
const { DataTypes, Sequelize } = require('sequelize');
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(CUSTOMER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
allowNull: false,
type: DataTypes.STRING,
},
lastName: {
allowNull: false,
type: DataTypes.STRING,
field: 'last_name',
},
phone: {
allowNull: false,
type: DataTypes.STRING,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'created_at',
defaultValue: Sequelize.NOW,
},
userId: {
field: 'user_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: USER_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable(CUSTOMER_TABLE);
},
};
Migración change-user-id.js
:
'use strict';
const { CUSTOMER_TABLE } = require('../models/customer.model');
const { DataTypes } = require('sequelize');
module.exports = {
up: async (queryInterface) => {
await queryInterface.changeColumn(CUSTOMER_TABLE, 'user_id', {
field: 'user_id',
allowNull: false,
type: DataTypes.INTEGER,
unique: true,
});
},
down: async (queryInterface) => {
// await queryInterface.dropTable(CUSTOMER_TABLE);
},
};
Migración order.js
:
'use strict';
const { ORDER_TABLE } = require('../models/order.model');
const { CUSTOMER_TABLE } = require('../models/customer.model');
const { DataTypes, Sequelize } = require('sequelize');
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(ORDER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
customerId: {
field: 'customer_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CUSTOMER_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'created_at',
defaultValue: Sequelize.NOW,
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable(ORDER_TABLE);
},
};
Con este curso me he dado cuenta de que el Frontend es definitivamente más fácil que el Backend 😅
Error ''VIRTUAL'
Con la configuración de ssl del video no me ha funcionado. Parece que para un pool si sirve poner:
dialectOptions: {
ssl: {
rejectUnauthorized: false
}
}
Pero me seguía dando un error de pg_hba.conf y ssl en off. He tenido que añadir:
dialectOptions: {
ssl: {
require: true,
rejectUnauthorized: false
}
}
Me seguía falando y encontré un aporte que indicaba que había que definir el modo:
heroku config:set PGSSLMODE=no-verify
Porfin de verdad con este curso sufri mucho pero creo que valio la pena se siente tambien poder decir lo logre, gracias platzi , gracias al profesor fue muy claro ayudo un monton y austedes la comunidad cada vez que me encontraba un problea casi siempre habia alguien comentando al respecto y ayudo mucho la verdad… a por mas si llegaste a leer esto es porque tu tambien lo lograste muchas felicidades para ti tambien.
Heroku no tienes planes gratuito a la fecha! hice mi deploy en render el repositorio de github
No me quedaré con la duda. Lo que entiendo es que las migraciones en la pratica nos servirán para que se creen todas las tablas en nuestra base de datos nueva. ¿Estoy bien en eso?
Si desde el inicio ya tengo claro como quiero que sea mi base de datos y no tengo que hacer fixes porque desde el inicio se lo que se iba a presentar. ¿Podría simplemente eliminar la creación de crear el rol puesto que si ya estaba en el esquema en la migración anterior se crearía?
No se si me hago entender, me gustaría salir de esa duda.
La funcionalidad del rango de precios de productos quedó mal 😦
Uff ya quiero trabajar en otro proyecto igual.
Vaya a mi me paso eso, pero hice el consejo que dio el profesor en la anterior clase, entre a la base de datos y borre la columna role, y volvi a lanzar la migracion para que cree la columna rol y solucionado
Mi solución al reto en clase 😄
"use strict";
const { DataTypes } = require("sequelize");
const {
CATEGORY_TABLE,
CategorySchema,
} = require("../models/categories.model");
const { CustomerSchema, CUSTOMER_TABLE } = require("../models/customers.model");
const {
ORDER_PRODUCT_TABLE,
OrderProductSchema,
} = require("../models/orders-products.model");
const { ORDER_TABLE } = require("../models/orders.model");
const { PRODUCT_TABLE, ProductSchema } = require("../models/products.model");
const { USER_TABLE, UserSchema } = require("../models/users.model");
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface) {
await queryInterface.createTable(CATEGORY_TABLE, CategorySchema);
await queryInterface.createTable(PRODUCT_TABLE, ProductSchema);
await queryInterface.createTable(USER_TABLE, UserSchema);
await queryInterface.createTable(CUSTOMER_TABLE, CustomerSchema);
await queryInterface.createTable(ORDER_TABLE, {
id: {
allowNull: false,
primaryKey: true,
autoIncrement: true,
type: DataTypes.INTEGER,
},
customerId: {
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CUSTOMER_TABLE,
key: "id",
},
onUpdate: "CASCADE",
onDelete: "CASCADE",
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE,
},
});
await queryInterface.createTable(ORDER_PRODUCT_TABLE, OrderProductSchema);
},
async down(queryInterface) {
await queryInterface.dropTable(ORDER_PRODUCT_TABLE);
await queryInterface.dropTable(ORDER_TABLE);
await queryInterface.dropTable(CUSTOMER_TABLE);
await queryInterface.dropTable(USER_TABLE);
await queryInterface.dropTable(PRODUCT_TABLE);
await queryInterface.dropTable(CATEGORY_TABLE);
},
};
Para quienes tengan algo de conocimiento en base de datos prácticamente las migraciones es lo mismo que ejecutar DDL para modificar una base de datos relacional, por lo que igualmente a veces depende de las actualizaciones a aplicar se ejecuta un solo script con todos los comandos para las mismas… Y dichos comandos igualmente se debe ser bastante cuidadosos para primero analizar las tablas a modificar y en base a ello realizar los ajustes, por lo que haciendo un paralelismo las migraciones son exactamente lo mismo solo que sin aplicar ningún comando SQL/DDL
Les comparto como pude desplegar mi API de forma gratuita:
Aloje mi PostgresDB en Vercel que da una opción gratuita hasta 256mb.
En cuanto al Node.js lo puede subir a Cyclic.
Recuerden configurar el Enviroment que provee Vercel para que se pueda conectar correctamente,
Por último hacen la migración completa de la arquitectura de la tabla a VercelDB.
Después de esto ya pueden empezar a usar Insómania para manipular la API.
Saludos!!
DE ESTA MANERA SUBIMOS A Railway:
De esta manera pude yo subirlo a Railway sin borrar el .env del .gitignore Configure las migraciones a una sola:
'use strict';
const { DataTypes, Sequelize } = require('sequelize');
const { USER_TABLE } = require('./../models/user.model');
const { CUSTOMER_TABLE } = require('./../models/customer.model');
const { CATEGORY_TABLE } = require('./../models/category.model');
const { PRODUCT_TABLE } = require('./../models/product.model');
const { ORDER_TABLE } = require('./../models/order.model');
const { ORDER_PRODUCT_TABLE } = require('./../models/order-product.model');
/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(USER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
email: {
allowNull: false,
type: DataTypes.STRING,
unique: true,
},
password: {
allowNull: false,
type: DataTypes.STRING,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'create_at',
defaultValue: Sequelize.NOW,
},
});
await queryInterface.createTable(CUSTOMER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
firstName: {
allowNull: false,
type: DataTypes.STRING,
field: 'first_name',
},
lastName: {
allowNull: false,
type: DataTypes.STRING,
field: 'last_name',
},
phone: {
allowNull: true,
type: DataTypes.STRING,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'create_at',
defaultValue: Sequelize.NOW,
},
userId: {
field: 'user_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: USER_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
});
await queryInterface.createTable(CATEGORY_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
allowNull: false,
unique: true,
type: DataTypes.STRING,
},
image: {
allowNull: false,
type: DataTypes.STRING,
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.NOW,
field: 'create_at',
type: DataTypes.DATE,
},
});
await queryInterface.createTable(PRODUCT_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
name: {
allowNull: false,
type: DataTypes.STRING,
},
image: {
allowNull: false,
type: DataTypes.STRING,
},
description: {
allowNull: false,
type: DataTypes.TEXT,
},
price: {
allowNull: false,
type: DataTypes.INTEGER,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'create_at',
defaultValue: Sequelize.NOW,
},
categoryId: {
field: 'category_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CATEGORY_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
});
await queryInterface.createTable(ORDER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
customerId: {
field: 'customer_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CUSTOMER_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.NOW,
field: 'create_at',
type: DataTypes.DATE,
},
});
await queryInterface.createTable(ORDER_PRODUCT_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'create_at',
defaultValue: Sequelize.NOW,
},
amount: {
allowNull: false,
type: DataTypes.INTEGER,
},
orderId: {
field: 'order_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: ORDER_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
productId: {
field: 'product_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: PRODUCT_TABLE,
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable(USER_TABLE);
await queryInterface.dropTable(CUSTOMER_TABLE);
await queryInterface.dropTable(CATEGORY_TABLE);
await queryInterface.dropTable(PRODUCT_TABLE);
await queryInterface.dropTable(ORDER_TABLE);
await queryInterface.dropTable(ORDER_PRODUCT_TABLE);
},
};
Se crean un archivo **Dockerfile ** en la raíz del proyecto:
FROM node:16 AS build
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ARG NODE_ENV
ARG DATABASE_URL
ENV NODE_ENV=$NODE_ENV
ENV DATABASE_URL=$DATABASE_URL
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run migrations:run
EXPOSE 8080
CMD ["npm", "start"]
Primero creamos una DB en Railway y copiamos la URL de conexión en su apartado de conexión , le dan a New , luego Database - Postgres y le tiene que quedar como la imagen, Las variables de entorno se configuran automaticamente:
Una opción de deploy es Railway yo lo use así:
'use strict';
const { DataTypes, Sequelize } = require('sequelize');
const { USER_TABLE, UserSchema } = require('./../models/user.model')
const { CUSTOMER_TABLE, CustomerSchema } = require('./../models/customer.model')
const { CATEGORY_TABLE, CategorySchema } = require('./../models/category.model')
const { PRODUCT_TABLE, ProductSchema } = require('./../models/product.model')
const { ORDER_TABLE } = require('./../models/order.model')
const { OrderProductSchema, ORDER_PRODUCT_TABLE } = require('./../models/order-product')
module.exports = {
up: async (queryInterface) => {
await queryInterface.createTable(USER_TABLE, UserSchema);
await queryInterface.createTable(CUSTOMER_TABLE, CustomerSchema);
await queryInterface.createTable(CATEGORY_TABLE, CategorySchema);
await queryInterface.createTable(PRODUCT_TABLE, ProductSchema);
await queryInterface.createTable(ORDER_TABLE, {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
customerId: {
field: 'customer_id',
allowNull: false,
type: DataTypes.INTEGER,
references: {
model: CUSTOMER_TABLE,
key: 'id'
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL'
},
createdAt: {
allowNull: false,
type: DataTypes.DATE,
field: 'created_at',
defaultValue: Sequelize.NOW,
},
});
await queryInterface.createTable(ORDER_PRODUCT_TABLE, OrderProductSchema);
},
down: async (queryInterface) => {
await queryInterface.dropTable(USER_TABLE)
await queryInterface.dropTable(CUSTOMER_TABLE)
await queryInterface.dropTable(PRODUCT_TABLE)
await queryInterface.dropTable(CATEGORY_TABLE)
await queryInterface.dropTable(ORDER_TABLE)
await queryInterface.dropTable(ORDER_PRODUCT_TABLE)
}
};
FROM node:16 AS build
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run migrations:run
EXPOSE 8080
CMD ["npm", "start"]
DATABASE_URL='urlderailway'
En nuestro .gitignore eliminamos el archivo .env y lo subimos a un repo de github. Y creamos el proyecto para nuestra aplicaión en Railway
Creamos el servicio y debería correr la migración sin problemas.
NOTA: esta opción nos dejará hacer el deploy pero es una mala practica ya que estaremos dejando expuesta nuestra varible de entorno al subir el archivo .env . Por lo que es solo una buena opción para aprender pero para producción no.
No se si se podrá ya que al momento de crear el proyecto del servicio railway nos permite pasarle variables de entorno pero por alguna razón (en mi caso) no detectaba o no le llegaba la variable de entorno, es por eso, por el momento que lo solucione así, si alguien pudo subirlo en Railway espero su feedback. Gracias.
Qué pasaria si, borro la migración de add-role y dejo que el create-user cree el modelo como esta definido ya con el role?
Tuve un error al crear la migración ya que me aparecia un error con el autoIncrement en la primera tabla.
El error era el siguente:
ERROR: syntax error at or near "SERIAL"
Se pudo corregir dandole el valor de autoIncrement: false, y se agrega la propiedad de defaultValue: UUIDV4: Quedando de la siguente forma
id: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: DataTypes.STRING,
defaultValue: UUIDV4,
}
Tomen en cuenta cambiar todas los id de este tipo, también con la llaves foráneas.
Esta clase es como un backlog y documento de lecciones aprendidas pero en formato mp4
El contenido del archivo de migración única:
'use strict';
const { UserSchema, USER_TABLE } = require('./../models/userModel');
const { CustomerSchema, CUSTOMER_TABLE } = require('./../models/customerModel');
const { ProductSchema, PRODUCT_TABLE } = require('./../models/productModel');
const { CategorySchema, CATEGORY_TABLE } = require('./../models/categoryModel');
const { OrderSchema, ORDER_TABLE } = require('./../models/orderModel');
const { OrderProductSchema, ORDER_PRODUCT_TABLE } = require('./../models/order-productModel');
module.exports = {
async up (queryInterface) {
await queryInterface.createTable(USER_TABLE, UserSchema);
await queryInterface.createTable(CUSTOMER_TABLE, CustomerSchema);
await queryInterface.createTable(PRODUCT_TABLE, ProductSchema);
await queryInterface.createTable(CATEGORY_TABLE, CategorySchema);
await queryInterface.createTable(ORDER_TABLE, OrderSchema);
await queryInterface.createTable(ORDER_PRODUCT_TABLE, OrderProductSchema);
},
async down (queryInterface) {
await queryInterface.dropTable(USER_TABLE);
await queryInterface.dropTable(CUSTOMER_TABLE);
await queryInterface.dropTable(PRODUCT_TABLE);
await queryInterface.dropTable(CATEGORY_TABLE);
await queryInterface.dropTable(ORDER_TABLE);
await queryInterface.dropTable(ORDER_PRODUCT_TABLE);
}
};
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?