node JS
Diseño Web

¿Qué es NodeJS?

El lenguaje de programación Javascript está cada vez más extendido. Aunque fundamentalmente se ocupa de las funcionalidades que tiene una página web del lado de lo que el usuario está viendo, con nodeJS podrás programar con Javascript del lado del servidor. En este curso veremos como configurarlo, hacer consultas a la base de datos, comenzar proyectos desde cero, usar sockets...

Node.js es una plataforma para el desarrollo de aplicaciones en entorno del servidor mediante programación Javascript.

Presenta grandes ventajas en la implementación de aplicaciones que deben responder en tiempo real.

Pretende disputar terreno a lenguajes de servidor como PHP, Java, C# etc.

Instalación de NodeJS

  1. En Windows o Mac: NodeJS.
  2. En linux:
    sudo apt-get install -y npm

Hola Mundo

hola-mundo.jsconsole.log("hola Mundo")

Ejecutamos:

node hola-mundo.js

Crear un nuevo proyecto

npm init

package.json

package.json{
	"name": "proyecto", // Indicamos el nombre del proyecto
	"version": "0.0.0", // Indicamos la versión del proyecto
	"private": true,
	"scripts": {
		"start": "node ./bin/www" //Indicamos el archivo que se debe ejecutar para arrancar el proyecto
	},
	"dependencies": { 
		"body-parser": "~1.13.2",
		"cookie-parser": "~1.3.5",
		"debug": "~2.2.0",
		"express": "~4.13.1",
		"hbs": "~3.1.0",
		"morgan": "~1.6.1",
		"serve-favicon": "~2.3.0"
	}
}  

Al ejecutar npm install, se busca el archivo 'package.json' y se procede a instalar todos los módulos especificados en la propiedad 'dependencies'.

Módulos

Crear módulos

Un módulo es un fichero donde se crean grupos de funciones

aritmetica.jsvar PI=3.14;
function dividir(x1,x2){
    if (x2==0){
        mostrarErrorDivision();
    }else{
        return x1/x2;
    }
}

function mostrarErrorDivision() {
    console.log('No se puede dividir por cero');
}
exports.dividir=dividir;
exports.PI=PI;
main.jsvar mat=require('./aritmetica');

console.log('La división de 8/4='+mat.dividir(8,4));
console.log('El valor de PI='+mat.PI);

Para lanzarlo todo:

node main.js

Un módulo también puede ser una carpeta que contiene un conjunto de ficheros y subcarpetas.

Módulos del núcleo de nodejs

Algunos de los módulos del núcleo de Node.js son: os, fs, http, url, net, path, process, dns etc..

prueba.jsvar os=require('os');
console.log('Sistema operativo:'+os.platform());
console.log('Versión del sistema operativo:'+os.release());
console.log('Memoria total:'+os.totalmem()+' bytes');
console.log('Memoria libre:'+os.freemem()+' bytes');

Módulo para administrar el sistema de archivos: fs

var fs=require('fs');
//writeFile(donde_vamos_a_escribir, que_vamos_a_escribir, que_vamos_a_ejecutar_despues_de_escribir)
fs.writeFile('./archivo.txt','aaaaaa\nbbbbbb',function(error){
if (error)
    console.log(error);
else
    console.log('El archivo fue creado');
});
console.log('Última línea del programa');

Como nodejs es asíncrono, veremos que antes de mostrarse el texto "El archivo fue creado" se muestra el texto "Última línea del programa".

Instalación de un módulo

npm install socket.io
forma abreviada:npm i -S socket.io
npm i --g socket.io

El parametro --g hace que el módulo se instale de forma global y esté disponible para todos los usuarios en todos los proyectos. No podremos ejecutar este comando si no somos root.

Tras instalar un módulo, será recomendable reiniciar el servidor para que los cambios surtan efecto

Módulo para actualizar los cambios con express

npm i nodemon
package.json"scripts":{
    // "start": "node ./bin/www"
    "start": "nodemon ./bin/www"
}

Para parar el servidor:

npm stop
app.jsprocess.title = myApp;
scripts.json"scripts": {
    "start": "app.js",
    "stop": "pkill --signal SIGINT myApp" /*process title*/
}

Generación de un proyecto que utilice express

Tenemos dos opciones:

  • Usar express-generator:

    La siguiente herramienta genera una estructura de carpetas y ficheros adecuada para realizar desarrollos con el módulo express. Para instalarla:

    npm i express-generator --g

    Para usuarla:

    express proyecto --hbs //hbs indica el sistema de plantillas a utilizar
  • Creando el proyecto con express manualmente (recomendado):
  • package.json{
      "name": "server",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "local": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch",
        "start-local": "nodemon --watch build --exec \"node build/bundle.js\"",
        "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
        "start": "node ./build/bundle.js",
        "upload-dinahosting": "npm run build && node ./bin/upload.js",
        "heroku": "npm run build && git add . && git commit -m 'subida' && git push heroku master"
      },
      "author": "Pablo Monteserín",
      "license": "ISC",
      "dependencies": {
        "@babel/runtime": "^7.5.5",
        "body-parser": "^1.19.0",
        "cors": "^2.8.5",
        "easy-ftp": "^0.4.1",
        "express": "^4.17.1",
        "jsonwebtoken": "^8.5.1",
        "mysql2": "^2.1.0",
        "sequelize": "^5.21.3",
        "socket.io": "^2.3.0"
      },
      "devDependencies": {
        "@babel/core": "^7.5.5",
        "@babel/plugin-proposal-class-properties": "^7.5.5",
        "@babel/plugin-proposal-decorators": "^7.7.4",
        "@babel/plugin-proposal-optional-chaining": "^7.2.0",
        "@babel/plugin-transform-runtime": "^7.5.5",
        "@babel/plugin-transform-spread": "^7.2.2",
        "@babel/preset-env": "^7.5.5",
        "@babel/runtime": "^7.5.5",
        "cross-env": "^5.2.0",
        "babel-cli": "^6.26.0",
        "babel-core": "^7.0.0-bridge.0",
        "babel-eslint": "^10.0.2",
        "babel-jest": "^24.8.0",
        "babel-loader": "^8.0.6",
        "nodemon": "^1.19.1",
        "terser-webpack-plugin": "^1.3.0",
        "webpack": "^4.37.0",
        "webpack-cli": "^3.3.6",
        "webpack-dev-server": "^3.7.2",
        "webpack-node-externals": "^1.7.2"
      }
    }
    webpack.config.jsconst path = require('path');
    const webpackNodeExternals = require('webpack-node-externals');
    const TerserPlugin = require('terser-webpack-plugin');
    
    const development = process.env.NODE_ENV === 'development';
    
    // Constant with our paths
    const paths = {
      OUTPUT: path.resolve(__dirname, 'build'),
    };
    
    module.exports = {
      entry: {
        bundle: './src/index.js',
      },
      target: 'node',
      node: {
        __filename: true,
        __dirname: true,
      },
      output: {
        path: paths.OUTPUT,
      },
      module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            use: 'babel-loader',
          },
        ],
      },
      resolve: {
        extensions: ['.js', '.jsx'],
      },
      optimization: {
        minimizer: [
          new TerserPlugin({
            sourceMap: development,
            cache: true,
            parallel: true,
            terserOptions: {
              compress: true,
              ecma: 6,
              mangle: true,
            },
          }),
        ],
      },
      externals: [webpackNodeExternals()],
      mode: process.env.NODE_ENV,
      devtool: development && 'source-map',
    };
    ./src/controllers/index.jsimport Api from './api/api';
    
    const Controllers = (app) => {
        Api(app);
    };
    
    export default Controllers;
    .babelrc{
        "presets": ["@babel/preset-env"],
        "plugins": [
          "@babel/plugin-transform-spread",
          "@babel/plugin-proposal-class-properties",
          "@babel/plugin-proposal-optional-chaining",
          "@babel/plugin-transform-runtime"
        ]
      }
    ./src/controllers/api/index.jsimport * as Model from '../../models';
    import { asyncHandler } from '../../middlewares/error-handler';
    import jwt from 'jsonwebtoken';
    
    const SECRET = 'uuuu';
    
    const StartApiServer = (app) => {
        app.get('/', function (req, res, next) {
            res.send("va bien la cosa")
        });
    
        app.post('/alumnoIntroduceCodigo', asyncHandler(async (req, res, next) => {
            const cod = req.body.cod;
            if (!cod) return res.sendStatus(400);
            const students = await Model.searchStudentsFromClass(cod.trim());
            res.send(students);
        }));
    }
    ./src/controllers/sockets/index.jsimport socketIO from 'socket.io';
    import * as Model from '../../models';
    import { socketHandler } from '../../middlewares/error-handler';
    
    const StartSocketServer = (server) => {
      const io = socketIO(server);
      io.on('connection', function (socket) {
        socket.on('alguienTermino', socketHandler(async (msg) => {
          await Model.heterminado(msg.alumnoId);
          io.emit('alguienTermino', msg);
      }));    
    }
    ./src/models/database.jsconst { Sequelize } = require('sequelize');
    
    const dbHeroku = {
        host: 'remote.com',
        user: 'remoteUser',
        password: 'remotePassword',
        database: 'remoteDB',
        connectionLimit : 10,
    };
    
    const dbLocal = {
        host: 'localhost',
        user: 'root',
        password: '',
        database: 'databaseLocal'
    }
    const { database, user, password, host } = dbHeroku;
    export default new Sequelize(database, user, password, {
        host,
        dialect: 'mysql',
    });
    ./src/models/student.jsimport db from './database';
    const { QueryTypes } = require('sequelize');
    
    export const searchStudentsFromClass = cod => db.query(
        'SELECT * FROM alumnos WHERE cod = :cod',
        { 
            replacements: { cod },
            type: QueryTypes.SELECT
        }
    )
    ./src/middlewares/error-handler.js/*Tenemos una función que recibe como parámetro la función de cada controlador,
    la ejecuta (resolve)
    y si  hay algún error, lo mostrará por consola y lanzará un 500
    */
    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();
    });
     
    export const socketHandler = controller => msg => Promise
      .resolve(controller(msg))
      .catch((err) => {
        console.error('Socket error:' + err);
    });
    ./src/middlewares/index.jsimport cors from 'cors';
    import bodyParser from 'body-parser';
    
    const Middlewares = (app) => {
        app.use(bodyParser.urlencoded());
        app.use(bodyParser.json());
        app.use(cors()) // Use this after the variable declaration
        app.use(function (req, res, next) {
            res.header('Access-Control-Allow-Origin', '*');
            res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            res.header('Access-Control-Allow-Credentials', true);
            res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
        
            if (req.method === 'OPTIONS') {
                res.end();
            } else {
                next();
            }
        });
    };
    
    export default Middlewares;
    ./src/index.jsimport express from 'express';
    import http from 'http';
    import Middlewares from './middlewares';
    import Controllers from './controllers';
    
    const app = express();
    /*
    Heroku utiliza la variable de entorno process.env.port para asignar el puerto donde se va ejecutar el servidor,
    por eso la hemos definido en esta plantilla
    */
    const port = process.env.PORT || 3005;
    
    Middlewares(app);
    Controllers(app);
    
    const server = http.createServer(app);
    
    server.listen(port, () => console.log(`Server listening to http://localhost:${port}`));
    

    Para arrancar nuestra aplicación en el puerto 3000:

    npm start

    Si queremos parar node:

    ps auxxx | grep node #Este comando nos permite ver los servicios de node que se están ejecutando
    kill -9 1480 8573 #Paramos los servicios de node activos

Ejercicios rutas

Crear un nuevo proyecto con nodejs que implemente los siguientes enrutamientos:

  • / -> Muestra una traza en la terminal
  • /init -> Carga una vista llamada inicio que muestra el texto "página de inicio"
  • /user-init -> Carga una vista llamada inicio-usuario que muestra el texto "hola [usuario]", donde [usuario] es un valor que recibimos del router]
  • /libro -> Muestra el código HTML
    <h2>Carga libro</h2>
    , pero sin cargar ningún fichero de vista. La palabra libro será un nuevo fichero enrutador definido dentro de la carpeta routes
  • /libro/alta -> Muestra la traza "formulario alta". La palabra libro será un nuevo fichero enrutador definido dentro de la carpeta routes.
icono de mandar un mailSOPORTE Usuarios Premium
Pablo Monteserín
contacta conmigoPablo Monteserín

Para dudas técnicas sobre los ejercicios de mis cursos es necesario tener una cuenta premium activa. Para cualquier otra cosa, puedes usar el formulario de la página de contacto.