Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.
Vamos a comentar la plantilla que tienes en
https://github.com/monteserin/nodejs-template
Características del proyecto
Esta plantilla es muy completa y esta adaptada a proyectos grandes que usen base datos con schema, sockets, MVC, etc.
A continuación te explico como usar una plantilla hecha con NodeJS para tus proyectos. Esta plantilla tiene las siguientes características:
- Programación funcional.
- Enfocada para hacer API Rest.
- Soporte para web sockets.
- Soporte para swagger.
Para instalarla:
npx degit monteserin/nodejs-template
Código fuente
Base de datos
En el fichero .env estableceremos los datos de conexión contra la base de datos y si esta se regenerará de nuevo con cada actualización.
./env
# DATABASE
DB_HOST = localhost
DB_USER = [user]
DB_PASSWORD = [password]
DB_NAME = [name]
DB_FORCE_CLEAN = true
# APP
PORT = 5000
API REST Controller
Los endpoints de la entidad user se gestionan en ./src/entities/user/controller/index.js
En ./src/entities/http-samples.js tienes algunos ejemplos de endpoints.
Ejemplo de petición:
router.get('/:id', asyncHandler(async (req, res) => {
const { query: { id } } = req;
const data = await Controller.getById(id);
res.send(data);
}));
asyncHandler
Se encarga de evitar que el servidor se caiga cuando se produzca una excepción (en nuestro caso usamos Heroku, aunque en local usemos nodemon y no se caerá).
Por otra parte, también nos evita tener que repetir el código de manejo de errores (try, catch) para cada petición. Sólo hará falta escribir dicho código dentro del asyncHandler.
El siguiente código:
app.get("/", asyncHandler(async (req, res) => {
res.seeeend("va bien la cosaa");
}));
daría un error que sería gestionado aquí:
./src/middlewares/error-handler.js
export const asyncHandler = controller => (req, res, next) => Promise
.resolve(controller(req, res, next))
.catch((err) => {
console.error('Request error:' + err.toString());
if (err) return res.sendStatus(500).send();
});
swagger
Podemos acceder a la documentación de la api REST (Swagger) mediante el path /docs.
Model
Cada uno de los modelos heredará de un modelo genérico. De esta forma, cada modelo tendrá los métodos de alta, baja, modificación y consulta del modelo genérico, además de los suyos propios:
/src/entities/user/model/index.js
import GenericModel from '@Application/repository/generic-model';
import Schema from './schema';
const Model = {
...GenericModel(Schema),
getByEmail: email => Schema.findOne({ where: { email } })
};
export default Model;
/src/entities/user/model/generic-model.js
const GenericModel = Model => ({
create(data) {
return Model.create(data);
},
get(conditions) {
return Model.findAll(conditions ? {where:conditions} : {});
},
getById(id) {
return Model.findOne({ where: { id } });
},
updateById(id, data) {
return Model.update(data, { where: { id } });
},
deleteById(id) {
return Model.destroy({ where: { id } });
},
findOrCreate(condition, newObj){
return Model.findOrCreate({where:condition, defaults: newObj});
}
});
export default GenericModel;
Y la estructura de cada uno de estos modelos estará en su correspondiente fichero schema:
/src/entities/user/model/schema.js
import { db, DataTypes } from '@Application/database';
export default db.define('user', {
email: DataTypes.STRING,
auth0Id: DataTypes.STRING,
});
Generación del modelo en la base de datos al arrancar la aplicación
Debemos cargar un controlador que a su vez cargue el modelo cuya estructura queremos persistir.
./src/controllers/index.js
export default function (app) {
app.use('/plays', PlaysController);
./src/controllers/plays/index.js
import * as PlaysModel from "@Models/plays";
Regenerar la base de datos
Muchas veces, como consecuencia de haber modificado nuestro modelo de datos, necesitaremos borrar la base de datos para que las tablas vuelvan a generarse. Esto nunca debe hacerse en producción. Para forzar el borrado de las tablas al arrancar la aplicación, usaremos {force: true}
./src/database/index.js
export default async (onConnect) => {
try {
await db.authenticate();
db.sync({ force: true});
...
Conexión a la base de datos:
Está en /src/application/database/index.js. Desde este fichero cargaremos los datos del fichero .env.
Consultas Many to Many
Schema De Classroom
const ClassRoom = db.define('classroom', {
...
});
ClassRoom.associate = ({ student }) => {
ClassRoom.belongsToMany(student, { through: 'classroomsusers', onDelete: 'cascade' });
};
Schema de Student
import {db} from '@Application/database';
const {DataTypes} = require('sequelize');
const Student = db.define('student', {
...
});
Student.associate = ({ classroom }) => {
Student.belongsToMany(classroom, { through: 'classroomsusers', onDelete: 'cascade' });
};
export default Student;
Definir la tabla intermedia con atributos de relación
Los atributos de relación que hemos añadido en este caso son hasTerminated y isInClassroom.
En la tabla que usamos para establecer las relaciones tendremos que definir expresamente el campo id.
import {db, DataTypes} from '@Application/database'
const ClassroomUser = db.define('classroomuser', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
hasTerminated: DataTypes.BOOLEAN,
isInClassroom: DataTypes.BOOLEAN
});
ClassroomUser.associate = ({classroom, student}) => {
ClassroomUser.belongsTo(classroom);
ClassroomUser.belongsTo(student);
};
export default ClassroomUser;
Configuración de las relaciones
export const setAssociations = (db) => {
Object.keys(db.models).forEach((modelName) => {
if ('associate' in db.models[modelName]) {
db.models[modelName].associate(db.models);
}
});
};
Operaciones con relaciones Many to Many
Consulta de una clase con sus respectivos alumnos
import Schema from './schema';
import Student from '../../student/model/schema';
const Model = {
getClassroomWithStudents(conditions){
return Schema.findOne({where:conditions,include: Student});
}
};
export default Model;
Crear un estudiante vinculado a una clase
const [classroom] = await ClassroomModel.get({teacherId:msg.teacherId, cod: msg.cod});
if (classroom) {
const student = await Model.create({auth0Id: msg.auth0Id, picture: msg.picture, name: msg.name});
// creamos la relación en la tabla de relaciones
ClassroomStudentModel.create({studentId: student.id, classroomId: classroom.id});
return classroom;
}
Pasos a seguir al crear una nueva entidad:
1. Duplicamos una entidad de la carpeta entities.
2. Añadimos el adapter de la nueva entidad a /src/entities/index.js.
3. Modificamos el Swagger (sobre todo las rutas).
Login
Desde el front, enviaremos el usuario y contraseña utilizando basic auth:
axios.post("http://localhost:8080/login",{},
{
headers: {
"Content-Type": "application/json",
Authorization: "basic " + btoa(userName + ":" + password),
},
}
).then((response) => console.log(response),(error) => console.log(error));
Desde el back, comprobaremos que el token enviado es válido.