Curso de NodeJS | Proyecto completo con Express

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

Esta plantilla es muy completa y esta adaptada a proyectos grandes que usen base datos con schema, sockets, MVC, etc.

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

Características del proyecto

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

El código de la plantilla

API REST Controller

Sus peticiones se gestionan en ./src/controllers/api/users/index.js

Ejemplo de petición:

router.delete("/:id", restrictedAccess,asyncHandler( async (req, res) => {
  const { params: { id } } = req;
  await UsersModel.deleteUser(id);
  res.send(`User id: ${id} deleted`);
}));

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

Sus métodos se gestionan en ./src/models/users/index.js.

export const deleteUser = id => UserSchema.destroy({ where: { id }});

Y su estructura en ./src/models/users/schema.js

import { db } from '../../database';
const { DataTypes } = require('sequelize');

const User = db.define('user', {
	password: DataTypes.STRING,
	username: DataTypes.STRING,
});

export default User;

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 que 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/config/database.js:

export default {
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'amor',
    connectionLimit : 10,
};

Sequelize

Schema básico

import {db, DataTypes} from '@Application/database';

const ClassroomStudent = db.define('classroomstudent', {

  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
    allowNull: false,
  },
  auth0Id: {
    type: DataTypes.STRING,
    unique: true
  },
  hasTerminated: {
    type: DataTypes.BOOLEAN,
    defaultValue: false
  },
  doubtTime: DataTypes.TIME
});

Crud básico

    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 } });
    },
    update(conditions, data) {
        return Model.update(data, { where: conditions });
    },
    deleteById(id) {
        return Model.destroy({ where: { id } });
    },
    findOrCreate(condition, newObj){
        return Model.findOrCreate({where:condition, defaults: newObj});
    }

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 el controlador de User.

2. Duplicamos el modelo de User.

3. Modificamos el modelo para que maneje la entidad correcta.

4. Modificamos el controlador para que cargue el modelo correcto y maneje el modelo correcto.

5. Modificamos el Swager (sobre todo las rutas).

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.