Cómo crear una aplicación de facturación ligera con Node: base de datos y API

Introducción

Índice
  1. Introducción
  • Prerrequisitos
  • Paso 1: Configuración del proyecto
  • Paso 2: Creación y conexión a la base de datos mediante SQLite
  • Paso 3: Creación de rutas de aplicaciones
    1. CORREO/register
    2. CORREO/login
    3. CORREO/invoice
    4. CONSEGUIR/invoice/user/{user_id}
    5. CONSEGUIR/invoice/user/{user_id}/{invoice_id}
  • Conclusión
  • Una factura es un documento de bienes y servicios proporcionados que una empresa puede presentar a sus clientes.

    Una herramienta de facturación digital deberá realizar un seguimiento de los clientes, registrar los servicios y los precios, actualizar el estado de las facturas pagadas y proporcionar una interfaz para visualizar las facturas. Esto requerirá CRUD (Crear, Leer, Actualizar, Eliminar), bases de datos y enrutamiento.

    Nota: Esta es la Parte 1 de una serie de 3 partes. El segundo tutorial es Cómo crear una aplicación de facturación liviana con Node: Interfaz de usuario . El tercer tutorial es Cómo crear una aplicación de facturación liviana con Vue y Node: Autenticación JWT y envío de facturas .

    En este tutorial, creará una aplicación de facturación con Vue y NodeJS . Esta aplicación realizará funciones como crear, enviar, editar y eliminar una factura.

    Prerrequisitos

    Para completar este tutorial, necesitarás:

    • Node.js instalado localmente, lo cual puedes hacer siguiendo Cómo instalar Node.js y crear un entorno de desarrollo local .
    • SQLite instalado localmente, lo cual puedes hacer siguiendo Cómo instalar y usar SQLite .
    • Será necesario descargar e instalar una herramienta como Postman para probar los puntos finales de la API.

    Nota: SQLite actualmente viene preinstalado en macOS y Mac OS X de forma predeterminada.

    Este tutorial fue verificado con Node v16.1.0, npmv7.12.1 y SQLite v3.32.3.

    Paso 1: Configuración del proyecto

    Ahora que tenemos todos los requisitos establecidos, el siguiente paso es crear el servidor backend para la aplicación. El servidor backend mantendrá la conexión a la base de datos.

    Comience creando un directorio para el nuevo proyecto:

    1. mkdir invoicing-app

    Navegue hasta el directorio del proyecto recién creado:

    1. cd invoicing-app

    Luego inicialícelo como un proyecto Node:

    1. npm init -y

    Para que el servidor funcione correctamente, hay algunos paquetes de Node que deben instalarse. Puede instalarlos ejecutando este comando:

    1. npm install bcrypt@5.0.1 bluebird@3.7.2 cors@2.8.5 express@4.17.1 lodash@4.17.21 multer@1.4.2 sqlite3@5.0.2^ umzug@2.3.0^

    Ese comando instala los siguientes paquetes:

    • bcryptpara codificar las contraseñas de los usuarios
    • bluebirdCómo usar Promesas al escribir migraciones
    • corspara compartir recursos de origen cruzado
    • expressPara potenciar nuestra aplicación web
    • lodashpara métodos de utilidad
    • multerPara gestionar solicitudes de formulario entrantes
    • sqlite3Para crear y mantener la base de datos
    • umzugcomo ejecutor de tareas para ejecutar nuestras migraciones de bases de datos

    Nota: Desde la publicación original, este tutorial se actualizó para incluir lodash. isEmpty()La biblioteca de middleware para el manejo multipart/form-datase cambió de connect-multipartya multer.

    Crea un server.jsarchivo que contendrá la lógica de la aplicación. En el server.jsarchivo, importa los módulos necesarios y crea una aplicación Express:

    servidor.js

    const express = require('express');const cors = require('cors');const sqlite3 = require('sqlite3').verbose();const PORT = process.env.PORT || 3128;const app = express();app.use(express.urlencoded({extended: false}));app.use(express.json());app.use(cors());// ...

    Crea una /ruta para probar que el servidor funciona:

    servidor.js

    // ...app.get('/', function(req, res) {  res.send('Welcome to Invoicing App.');});

    app.listen()Le dice al servidor el puerto que debe escuchar para las rutas entrantes:

    servidor.js

    // ...app.listen(PORT, function() {  console.log(`App running on localhost:${PORT}.`);});

    Para iniciar el servidor, ejecute lo siguiente en el directorio de su proyecto:

    1. node server

    Su aplicación ahora comenzará a escuchar las solicitudes entrantes.

    Paso 2: Creación y conexión a la base de datos mediante SQLite

    Para una aplicación de facturación, se necesita una base de datos para almacenar las facturas existentes. SQLite será el cliente de base de datos elegido para esta aplicación.

    Comience creando una databasecarpeta:

    1. mkdir database

    Ejecute el sqlite3cliente y cree un InvoicingApp.dbarchivo para su base de datos en este nuevo directorio:

    1. sqlite3 database/InvoicingApp.db

    Ahora que se ha seleccionado la base de datos, el siguiente paso es crear las tablas necesarias.

    Esta aplicación utilizará tres tablas:

    • “Usuarios”: aquí se incluirán los datos del usuario ( id, name, email, company_name, password)
    • “Facturas”: almacena datos de una factura ( id, name, paid, user_id)
    • “Transacciones”: Transacciones singulares que se unen para formar una factura ( name, price, invoice_id)

    Una vez identificadas las tablas necesarias, el siguiente paso es ejecutar las consultas para crear las tablas.

    Las migraciones se utilizan para realizar un seguimiento de los cambios en una base de datos a medida que la aplicación crece. Para ello, cree una migrationscarpeta en el databasedirectorio.

    1. mkdir database/migrations

    Esta será la ubicación de todos los archivos de migración.

    Ahora, crea un 1.0.jsarchivo en la migrationscarpeta. Esta convención de nombres sirve para mantener un registro de los cambios más recientes.

    En el 1.0.jsarchivo, primero importa los módulos de nodo:

    base de datos/migraciones 1.0.js

    "use strict";const path = require('path');const Promise = require('bluebird');const sqlite3 = require('sqlite3');// ...

    Luego, exporte una upfunción que se ejecutará cuando se ejecute el archivo de migración y una downfunción para revertir los cambios en la base de datos.

    base de datos/migraciones/1.0.js

    // ...module.exports = {  up: function() {    return new Promise(function(resolve, reject) {      let db = new sqlite3.Database('./database/InvoicingApp.db');      db.run(`PRAGMA foreign_keys = ON`);      // ...

    En la upfunción, primero se realiza la conexión a la base de datos. Luego, se habilitan las claves externas en la sqlitebase de datos. En SQLite, las claves externas están deshabilitadas de manera predeterminada para permitir la compatibilidad con versiones anteriores, por lo que las claves externas deben habilitarse en cada conexión.

    A continuación, especifique las consultas para crear las tablas:

    base de datos/migraciones/1.0.js

    // ...      db.serialize(function() {        db.run(`CREATE TABLE users (          id INTEGER PRIMARY KEY,          name TEXT,          email TEXT,          company_name TEXT,          password TEXT        )`);        db.run(`CREATE TABLE invoices (          id INTEGER PRIMARY KEY,          name TEXT,          user_id INTEGER,          paid NUMERIC,          FOREIGN KEY(user_id) REFERENCES users(id)        )`);        db.run(`CREATE TABLE transactions (          id INTEGER PRIMARY KEY,          name TEXT,          price INTEGER,          invoice_id INTEGER,          FOREIGN KEY(invoice_id) REFERENCES invoices(id)        )`);      });      db.close();    });  }}

    La serialize()función se utiliza para especificar que las consultas se ejecutarán secuencialmente y no simultáneamente.

    Una vez creados los archivos de migración, el siguiente paso es ejecutarlos para realizar los cambios en la base de datos. Para ello, crea una scriptscarpeta desde la raíz de tu aplicación:

    1. mkdir scripts

    Luego, crea un archivo llamado migrate.jsen este nuevo directorio y agrega lo siguiente al migrate.jsarchivo:

    scripts/migrate.js

    const path = require('path');const Umzug = require('umzug');let umzug = new Umzug({  logging: function() {    console.log.apply(null, arguments);  },  migrations: {    path: './database/migrations',    pattern: /.js$/  },  upName: 'up'});// ...

    En primer lugar, se importan los módulos de nodo necesarios. A continuación, umzugse crea un nuevo objeto con las configuraciones. pathTambién patternse especifican los scripts de migración. Para obtener más información sobre las configuraciones, consulte el archivo umzugREADME .

    Para proporcionar también una retroalimentación detallada, cree una función para registrar eventos como se muestra a continuación y luego, finalmente, ejecute la upfunción para ejecutar las consultas de base de datos especificadas en la carpeta de migraciones:

    scripts/migrate.js

    // ...function logUmzugEvent(eventName) {  return function(name, migration) {    console.log(`${name} ${eventName}`);  };}// using event listeners to log eventsumzug.on('migrating', logUmzugEvent('migrating'));umzug.on('migrated', logUmzugEvent('migrated'));umzug.on('reverting', logUmzugEvent('reverting'));umzug.on('reverted', logUmzugEvent('reverted'));// this will run your migrationsumzug.up().then(console.log('all migrations done'));

    Ahora, para ejecutar el script, ve a tu terminal y en el directorio raíz de tu aplicación, ejecuta:

    1. node scripts/migrate.js

    Verá un resultado similar al siguiente:

    Outputall migrations done== 1.0: migrating =======1.0 migrating

    En este punto, la ejecución del migrate.jsscript ha aplicado la 1.0.jsconfiguración a InvoicingApp.db.

    Paso 3: Creación de rutas de aplicaciones

    Ahora que la base de datos está configurada adecuadamente, el siguiente paso es volver al server.jsarchivo y crear las rutas de la aplicación. Para esta aplicación, se pondrán a disposición las siguientes rutas:

    URL MÉTODO FUNCIÓN
    /register POST Para registrar un nuevo usuario
    /login POST Para iniciar sesión con un usuario existente
    /invoice POST Para crear una nueva factura
    /invoice/user/{user_id} GET Para obtener todas las facturas de un usuario
    /invoice/user/{user_id}/{invoice_id} GET Para obtener una determinada factura
    /invoice/send POST Para enviar factura al cliente

    CORREO/register

    Para registrar un nuevo usuario, se realizará una solicitud POST a la /registerruta de su servidor.

    Revise server.jsy agregue las siguientes líneas de código:

    servidor.js

    // ...const _ = require('lodash');const multer  = require('multer');const upload = multer();const bcrypt = require('bcrypt');const saltRounds = 10;// POST /register - beginapp.post('/register', upload.none(), function(req, res) {  // check to make sure none of the fields are empty  if (    _.isEmpty(req.body.name)    || _.isEmpty(req.body.email)    || _.isEmpty(req.body.company_name)    || _.isEmpty(req.body.password)  ) {    return res.json({      "status": false,      "message": "All fields are required."    });  }  // any other intended checks// ...

    Se comprueba si alguno de los campos está vacío y si los datos enviados coinciden con todas las especificaciones. Si se produce un error, se envía un mensaje de error al usuario como respuesta. En caso contrario, se codifica la contraseña y los datos se almacenan en la base de datos y se envía una respuesta al usuario informándole de que está registrado.

    servidor.js

    // ...    bcrypt.hash(req.body.password, saltRounds, function(err, hash) {    let db = new sqlite3.Database('./database/InvoicingApp.db');    let sql = `INSERT INTO                users(                  name,                  email,                  company_name,                  password                )                VALUES(                  '${req.body.name}',                  '${req.body.email}',                  '${req.body.company_name}',                  '${hash}'                )`;    db.run(sql, function(err) {      if (err) {        throw err;      } else {        return res.json({          "status": true,          "message": "User Created."        });      }    });    db.close();  });});// POST /register - end

    Ahora, si usamos una herramienta como Postman para enviar una solicitud POST a /registercon name, email, company_name, y password, creará un nuevo usuario:

    Llave Valor
    name Usuario de prueba
    email example@example.com
    company_name Empresa de prueba
    password contraseña

    Podemos utilizar una consulta y mostrar la Userstabla para verificar la creación del usuario:

    1. select * from users;

    La base de datos ahora contiene un usuario recién creado:

    Output1|Test User|example@example.com|Test Company|[hashed password]

    Tu /registerruta ahora está verificada.

    CORREO/login

    Si un usuario existente intenta iniciar sesión en el sistema mediante la /loginruta, deberá proporcionar su dirección de correo electrónico y contraseña. Una vez que lo haga, la ruta gestionará la solicitud de la siguiente manera:

    servidor.js

    // ...// POST /login - beginapp.post('/login', upload.none(), function(req, res) {  let db = new sqlite3.Database('./database/InvoicingApp.db');  let sql = `SELECT * from users where email='${req.body.email}'`;  db.all(sql, [], (err, rows) = {    if (err) {      throw err;    }    db.close();    if (rows.length == 0) {      return res.json({        "status": false,        "message": "Sorry, wrong email."      });    }// ...

    Se realiza una consulta a la base de datos para obtener el registro del usuario con un correo electrónico determinado. Si el resultado devuelve una matriz vacía, significa que el usuario no existe y se envía una respuesta informando al usuario del error.

    Si la consulta a la base de datos devuelve datos del usuario, se realiza una comprobación adicional para ver si la contraseña ingresada coincide con la contraseña registrada en la base de datos. Si es así, se envía una respuesta con los datos del usuario.

    servidor.js

    // ...    let user = rows[0];    let authenticated = bcrypt.compareSync(req.body.password, user.password);    delete user.password;    if (authenticated) {      return res.json({        "status": true,        "user": user      });    }    return res.json({      "status": false,      "message": "Wrong password. Please retry."    });  });});// POST /login - end// ...

    Cuando se prueba la ruta, recibirá un resultado exitoso o fallido.

    Ahora, si usamos una herramienta como Postman para enviar una solicitud POST a /logincon emaily password, enviará una respuesta.

    Llave Valor
    email example@example.com
    password contraseña

    Como este usuario existe en la base de datos, obtenemos la siguiente respuesta:

    Output{    "status": true,    "user": {        "id": 1,        "name": "Test User",        "email": "example@example.com",        "company_name": "Test Company"    }}

    Tu /loginruta ahora está verificada.

    CORREO/invoice

    La /invoiceruta se encarga de la creación de una factura. Los datos que se pasan a la ruta incluyen el ID de usuario, el nombre de la factura y el estado de la misma. También incluye las transacciones singulares que componen la factura.

    El servidor maneja la solicitud de la siguiente manera:

    servidor.js

    // ...// POST /invoice - beginapp.post('/invoice', upload.none(), function(req, res) {  // validate data  if (_.isEmpty(req.body.name)) {    return res.json({      "status": false,      "message": "Invoice needs a name."    });  }  // perform other checks// ...

    En primer lugar, se validan los datos enviados al servidor. A continuación, se establece una conexión con la base de datos para las consultas posteriores.

    servidor.js

    // ...  // create invoice  let db = new sqlite3.Database('./database/InvoicingApp.db');  let sql = `INSERT INTO invoices(                name,                user_id,                paid              )              VALUES(                '${req.body.name}',                '${req.body.user_id}',                0              )`;// ...

    INSERTSe escribe y ejecuta la consulta necesaria para crear la factura. Posteriormente, se insertan las transacciones singulares en la transactionstabla con la invoice_idcomo clave externa para hacer referencia a ellas.

    servidor.js

    // ...  db.serialize(function() {    db.run(sql, function(err) {      if (err) {        throw err;      }      let invoice_id = this.lastID;      for (let i = 0; i  req.body.txn_names.length; i++) {        let query = `INSERT INTO                      transactions(                        name,                        price,                        invoice_id                      ) VALUES(                        '${req.body.txn_names[i]}',                        '${req.body.txn_prices[i]}',                        '${invoice_id}'                      )`;        db.run(query);      }      return res.json({        "status": true,        "message": "Invoice created."      });    });  });});// POST /invoice - end// ...

    Ahora, si usamos una herramienta como Postman para enviar una solicitud POST a /invoicecon name, user_id, txn_names, y txn_prices, creará una nueva factura y registrará las transacciones:

    Llave Valor
    name Factura de prueba
    user_id 1
    txn_names iPhone
    txn_prices 600
    txt_names MacBook
    txn_prices 1700

    A continuación, revise la tabla Facturas:

    1. select * from invoices;

    Observe el siguiente resultado:

    Output1|Test Invoice|1|0

    Ejecute el siguiente comando:

    1. select * from transactions;

    Observe el siguiente resultado:

    Output1|iPhone|600|12|Macbook|1700|1

    Tu /invoiceruta ahora está verificada.

    CONSEGUIR/invoice/user/{user_id}

    Ahora, cuando un usuario quiere ver todas las facturas creadas, el cliente realizará una GETsolicitud a la /invoice/user/:idruta. user_idSe pasa como parámetro de ruta. La solicitud se gestiona de la siguiente manera:

    índice.js

    // ...// GET /invoice/user/:user_id - beginapp.get('/invoice/user/:user_id', upload.none(), function(req, res) {  let db = new sqlite3.Database('./database/InvoicingApp.db');  let sql = `SELECT * FROM invoices WHERE user_id='${req.params.user_id}' ORDER BY invoices.id`;  db.all(sql, [], (err, rows) = {    if (err) {      throw err;    }    return res.json({      "status": true,      "invoices": rows    });  });});// GET /invoice/user/:user_id - end// ...

    Se ejecuta una consulta para obtener todas las facturas y las transacciones relacionadas con la factura que pertenecen a un usuario en particular.

    Considere una solicitud de todas las facturas de un usuario:

    localhost:3128/invoice/user/1

    Responderá con los siguientes datos:

    Output{"status":true,"invoices":[{"id":1,"name":"Test Invoice","user_id":1,"paid":0}]}

    Tu /invoice/user/:user_idruta ahora está verificada.

    CONSEGUIR/invoice/user/{user_id}/{invoice_id}

    Para obtener una factura específica, GETse realiza una solicitud con user_idy invoice_ida la /invoice/user/{user_id}/{invoice_id}ruta. La solicitud se gestiona de la siguiente manera:

    índice.js

    // ...// GET /invoice/user/:user_id/:invoice_id - beginapp.get('/invoice/user/:user_id/:invoice_id', upload.none(), function(req, res) {  let db = new sqlite3.Database('./database/InvoicingApp.db');  let sql = `SELECT * FROM invoices LEFT JOIN transactions ON invoices.id=transactions.invoice_id WHERE user_id='${req.params.user_id}' AND invoice_id='${req.params.invoice_id}' ORDER BY transactions.id`;  db.all(sql, [], (err, rows) = {    if (err) {      throw err;    }    return res.json({      "status": true,      "transactions": rows    });  });});// GET /invoice/user/:user_id/:invoice_id - end// set application port// ...

    Se ejecuta una consulta para obtener una sola factura y las transacciones relacionadas con la factura que pertenece al usuario.

    Considere una solicitud de una factura específica para un usuario:

    localhost:3128/invoice/user/1/1

    Responderá con los siguientes datos:

    Output{"status":true,"transactions":[{"id":1,"name":"iPhone","user_id":1,"paid":0,"price":600,"invoice_id":1},{"id":2,"name":"Macbook","user_id":1,"paid":0,"price":1700,"invoice_id":1}]}

    Tu /invoice/user/:user_id/:invoice_idruta ahora está verificada.

    Conclusión

    En este tutorial, configurará su servidor con todas las rutas necesarias para una aplicación de facturación liviana.

    Continúe su aprendizaje con Cómo crear una aplicación de facturación liviana con Node: Interfaz de usuario .

    SUSCRÍBETE A NUESTRO BOLETÍN 
    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Subir

    Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion