Uso de Sequelize.js y SQLite en una aplicación Express.js

    En este tutorial estaré demostrando cómo construir una aplicación web de administración de contactos simple usando Node.js , Express.js , Vue.js junto con el mapeador relacional de objetos (ORM) sequelize.js respaldado por una base de datos SQLite .

    Sin embargo, el enfoque principal de este artículo será cómo usar la biblioteca sequelize.js junto con SQLite, que amplía mi artículo anterior Un tutorial de SQLite con Node.js. Puede encontrar el código completo para este tutorial en mi cuenta de GitHub .

    Configuración e instalación

    Para comenzar, inicializaré un nuevo proyecto usando good ole npm inity presionando enter para aceptar todos los valores predeterminados, excepto para usar el punto de entrada de server.js en lugar de index.js. Como nota al margen, estoy usando Node.js versión 8.10.

    $ mkdir node-vue-sqlite-sequelize && cd node-vue-sqlite-sequelize
    $ npm init
    

    A continuación, instalaré las dependencias que necesitaré, que son express, body-parser, sequelize, sequelize-cli, sqlite3 y, opcionalmente, nodemon para evitar que tenga que reiniciar Node.js todo el tiempo.

    $ npm install --save express body-parser sequelize sequelize-cli sqlite3 nodemon
    

    Creación de la interfaz de usuario de administración de contactos

    Primero hago un directorio estático en el mismo directorio que el archivo package.json:

    $ mkdir static
    

    Dentro del nuevo directorio estático crearé un archivo HTML llamado index.html. Para crear interfaces de usuario web ricas e interactivas (o incluso aplicaciones de escritorio con Electron ) confío en Vue.js por su elegancia y simplicidad, así que nuevamente aquí en este tutorial usaré Vue.js en el archivo index.html.

    Dentro de index.html comenzaré con una pieza simple de HTML5 boiler-plate y fuente Vue.js junto con Bulma y Font-Awesome para hacer las cosas un poco más bonitas, junto con axios.js, que usaré para llamadas AJAX más adelante .

    <!-- index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Contact</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.css">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script>
      <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
      <style>
        .section-container {
          max-width: 800px;
          margin: auto;
        }
      </style>
    </head>
    <body>
      
    </body>
    </html>
    

    Bien, antes de que pueda profundizar en la construcción de la interfaz de usuario, necesito discutir la funcionalidad y pensar en las diferentes formas en que tendré que pasar datos de un lado a otro con el backend de esta aplicación basada en datos.

    Dado que la aplicación administra contactos, obviamente necesitaré alguna representación de una persona, a la que me referiré como un objeto de contacto. En cuanto a comportamientos, probablemente necesitaré lo siguiente:

    • Ver lista de todos los contactos
    • Añadir contactos
    • Ver detalles de un contacto
    • Actualizar los detalles de un contacto
    • Eliminar un contacto

    Funcionalidad CRUD bastante simple, ¿verdad?

    Además, como mencioné anteriormente, interactuaré con el backend a través de una API AJAX REST, así que permítanme también diseñar los puntos finales de la API que creo que necesitaré.

    RouteMethodFunctionality

    /api/contactsOBTENERRecuperar todos los contactos
    /api/contactsCORREOCrear contacto
    /api/contacts/:idPONERActualizar los detalles de un contacto
    /api/contacts/:idELIMINAREliminar un contacto

    Ahora que he establecido cuáles son las funcionalidades requeridas, puedo comenzar a juntar los componentes de la interfaz de usuario e implementar los comportamientos deseados de JavaScript.

    Para comenzar, crearé un componente llamado AddUpdateContactque puedo usar para agregar nuevos contactos a la aplicación o actualizar los existentes. Entonces, en la parte inferior de la bodyetiqueta HTML agrego una scriptetiqueta y el siguiente código JavaScript basado en Vue.js:

    <script>
      const AddUpdateContact = {
        props: ['contact', 'title'],
        data () {
          return {
            id: this.contact ? this.contact.id : null,
            firstName: this.contact ? this.contact.firstName : '',
            lastName: this.contact ? this.contact.lastName : '',
            phone: this.contact ? this.contact.phone : ''
          }
        },
        methods: {
          save() {
            this.$emit('save-contact', { id: this.id, firstName: this.firstName, lastName: this.lastName, phone: this.phone })
            if (!this.id) {
              this.firstName=""
              this.lastName=""
              this.phone=""
            }
          }
        },
        template: `
          <form class="form" @submit.prevent="save">
            <h2 class="subtitle">{{ title }}</h2>
            <div class="field">
                <label>First Name</label>
                <div class="control">
                  <input class="input" type="text" v-model="firstName">
                </div> 
            </div>
            <div class="field">
                <label>Last Name</label>
                <div class="control">
                  <input class="input" type="text" v-model="lastName">
                </div> 
            </div>
            <div class="field">
                <label>Phone</label>
                <div class="control">
                  <input class="input" type="text" v-model="phone">
                </div> 
            </div>
            <div class="field">
                <div class="control">
                  <button class="button is-success">Save</button>
                </div> 
            </div>
          </form>
        `
      }
    </script>
    

    El AddUpdateContactcomponente puede recibir dos accesorios: (1) un título y, (2) en el caso opcional donde se usará para actualizar un contacto existente, un objeto de contacto. Los datamiembros se inicializan en función de si el contactobjeto se pasa o no como accesorio o no. La templatesección contiene un formulario y campos de entrada para agregar nueva información de contacto o modificar una existente. A continuación, la sección de métodos contiene un único savemétodo que emite la información del contacto hasta un componente de los padres, lo cual es consistente con los de un solo sentido filosofía de Vue.js enlace de datos .

    A continuación, crearé un Contactcomponente dentro de la scriptetiqueta, que se verá así:

    <script>
      // omitting the AddUpdateContact component ...
      const Contact = {
        props: ['contact'],
        components: { 'add-update-contact': AddUpdateContact },
        data () {
          return {
            showDetail: false
          }
        },
        methods: {
          onAddOrUpdateContact(contact) {
            this.$emit('save-contact', contact)
          },
          deleteContact (contact) {
            this.$emit('delete-contact', contact)
          }
        },
        template: `
          <div class="card">
            <header class="card-header">
              <p @click="showDetail = !showDetail" class="card-header-title">
                {{ contact.firstName }} {{ contact.lastName }}
              </p>
              <a class="card-header-icon" @click.stop="deleteContact(contact)">
                <span class="icon">
                  <i class="fa fa-trash"></i>
                </span>
              </a>
            </header>
            <div v-show="showDetail" class="card-content">
                <add-update-contact title="Details" :contact="contact" @save-contact="onAddOrUpdateContact" />
            </div>
          </div>
        `
      }
    </script>
    

    El Contactcomponente recibirá un contactobjeto como accesorio que contiene los datos que se mostrarán en su plantilla. El Contactcomponente también utilizará el AddUpdateContactcomponente descrito anteriormente para mostrar los detalles del contacto, así como para darle al usuario la capacidad de actualizarlo.

    La funcionalidad de actualización es posible mediante el uso del método del controlador de eventos onAddOrUpdateContactque escucha el save-contactevento del AddUpdateContactcomponente, que luego propaga el evento en la cadena hasta la instancia principal de Vue.js, que se discutirá en breve. Además, también hay un deleteContactmétodo del componente Contact que también propaga el mensaje de eliminación hasta la instancia principal de Vue.js.

    Ok, para resumir el código JavaScript de la interfaz, necesito crear una instancia del objeto raíz Vue.js y decirle que se vincule a un divelemento con la id“aplicación” así:

    <script>
      // omitting AddUpdateContant and Contact components ...
      new Vue({
        el: '#app',
        components: { contact: Contact, 'add-update-contact': AddUpdateContact },
        data: {
          contacts: [],
          apiURL: 'http://localhost:3000/api/contacts'
        },
        methods: {
          onAddOrUpdateContact (contact) {
            if (contact.id) {
              this.updateContact(contact)
            } else {
              this.addContact(contact)
            }
          },
          addContact (contact) {
            return axios.post(this.apiURL, contact)
              .then((response) => {
                const copy = this.contacts.slice()
                copy.push(response.data)
                this.contacts = copy
              })
          },
          updateContact (contact) {
            return axios.put(`${this.apiURL}/${contact.id}`, contact)
              .then((response) => {
                const copy = this.contacts.slice()
                const idx = copy.findIndex((c) => c.id === response.data.id)
                copy[idx] = response.data
                this.contacts = copy
              })
          },
          deleteContact (contact) {
            console.log('deleting', contact)
            return axios.delete(`${this.apiURL}/${contact.id}`)
              .then((response) => {
                let copy = this.contacts.slice()
                const idx = copy.findIndex((c) => c.id === response.data.id)
                copy.splice(idx, 1)
                this.contacts = copy
              })
          }
        },
        beforeMount () {
          axios.get(this.apiURL)
            .then((response) => {
              this.contacts = response.data
            })
        }
      })
    </script>
    

    El objeto raíz Vue.js registra los componentes descritos anteriormente y define un conjunto de métodos que interactuarán con la API REST a través de llamadas AJAX realizadas al servidor de aplicaciones Node.js / Express.js que se construirá próximamente.

    La última parte de la interfaz de usuario de la que hay que ocuparse es el HTML necesario para representar los componentes de Vue.js, que se muestra a continuación en la totalidad del archivo index.html.

    <!-- index.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Contacts</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.css">
      <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script>
      <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
      <style>
        .section-container {
          max-width: 800px;
          margin-right: auto;
          margin-left: auto;
        }
      </style>
    </head>
    <body>
      <div id="app" class="container">
        <section class="section section-container" style="padding-top: 24px; padding-bottom: 5px;">
            <h2 class="title">Contacts</h2>
            <contact v-for="contact in contacts"
                :key="contact.name"
                :contact="contact"
                @save-contact="onAddOrUpdateContact" 
                @delete-contact="deleteContact" />
        </section>
        <section class="section section-container" style="padding-bottom: 10px;">
          <div class="box">
            <add-update-contact title="Add Contact" @save-contact="onAddOrUpdateContact" />
          </div>
        </section>
      </div>
      <script>
      const AddUpdateContact = {
        props: ['contact', 'title'],
        data () {
          return {
            id: this.contact ? this.contact.id : null,
            firstName: this.contact ? this.contact.firstName : '',
            lastName: this.contact ? this.contact.lastName : '',
            phone: this.contact ? this.contact.phone : ''
          }
        },
        methods: {
          save() {
            this.$emit('save-contact', { id: this.id, firstName: this.firstName, lastName: this.lastName, phone: this.phone })
            if (!this.id) {
              this.firstName=""
              this.lastName=""
              this.phone=""
            }
          }
        },
        template: `
          <form class="form" @submit.prevent="save">
            <h2 class="subtitle">{{ title }}</h2>
            <div class="field">
                <label>First Name</label>
                <div class="control">
                  <input class="input" type="text" v-model="firstName">
                </div> 
            </div>
            <div class="field">
                <label>Last Name</label>
                <div class="control">
                  <input class="input" type="text" v-model="lastName">
                </div> 
            </div>
            <div class="field">
                <label>Phone</label>
                <div class="control">
                  <input class="input" type="text" v-model="phone">
                </div> 
            </div>
            <div class="field">
                <div class="control">
                  <button class="button is-success">Save</button>
                </div> 
            </div>
          </form>
        `
      }
    
      const Contact = {
        props: ['contact'],
        components: { 'add-update-contact': AddUpdateContact },
        data () {
          return {
            showDetail: false
          }
        },
        methods: {
          onAddOrUpdateContact(contact) {
            this.$emit('save-contact', contact)
          },
          deleteContact (contact) {
            this.$emit('delete-contact', contact)
          }
        },
        template: `
          <div class="card">
            <header class="card-header">
              <p @click="showDetail = !showDetail" class="card-header-title">
                {{ contact.firstName }} {{ contact.lastName }}
              </p>
              <a class="card-header-icon" @click.stop="deleteContact(contact)">
                <span class="icon">
                  <i class="fa fa-trash"></i>
                </span>
              </a>
            </header>
            <div v-show="showDetail" class="card-content">
                <add-update-contact title="Details" :contact="contact" @save-contact="onAddOrUpdateContact" />
            </div>
          </div>
        `
      }
    
      new Vue({
        el: '#app',
        components: { contact: Contact, 'add-update-contact': AddUpdateContact },
        data: {
          contacts: [],
          apiURL: 'http://localhost:3000/api/contacts'
        },
        methods: {
          onAddOrUpdateContact (contact) {
            if (contact.id) {
              this.updateContact(contact)
            } else {
              this.addContact(contact)
            }
          },
          addContact (contact) {
            return axios.post(this.apiURL, contact)
              .then((response) => {
                const copy = this.contacts.slice()
                copy.push(response.data)
                this.contacts = copy
              })
          },
          updateContact (contact) {
            return axios.put(`${this.apiURL}/${contact.id}`, contact)
              .then((response) => {
                const copy = this.contacts.slice()
                const idx = copy.findIndex((c) => c.id === response.data.id)
                copy[idx] = response.data
                this.contacts = copy
              })
          },
          deleteContact (contact) {
            console.log('deleting', contact)
            return axios.delete(`${this.apiURL}/${contact.id}`)
              .then((response) => {
                let copy = this.contacts.slice()
                const idx = copy.findIndex((c) => c.id === response.data.id)
                copy.splice(idx, 1)
                this.contacts = copy
              })
          }
        },
        beforeMount () {
          axios.get(this.apiURL)
            .then((response) => {
              this.contacts = response.data
            })
        }
      })
    
      </script>
    </body>
    </html>
    

    Andamiaje de la API REST

    Dado que estoy utilizando Express.js para esta aplicación, necesitaré crear un archivo JavaScript llamado server.js que sirva a la aplicación en el mismo directorio que el archivo package.json y luego abrirlo en mi editor de texto de desarrollo favorito (VS Código).

    En la parte superior del archivo, extraigo el marco Express.js a través requirey luego creo una instancia de la aplicación. A continuación, le digo a la aplicación que utilice el middleware estático de Express para ofrecer contenido estático (HTML, JS, CSS, et …) desde el directorio “estático”. En la parte inferior del script server.js le digo al servidor de aplicaciones que se vincule (escuche) al puerto 3000 y las solicitudes del servidor.

    Para distribuir los puntos finales de la API REST previamente definidos en la tabla de la sección anterior, necesito un analizador corporal e indico a la aplicación express que se utilizará para analizar el contenido del cuerpo HTTP. Luego elimino los puntos finales de la API que ofrecen el contenido solicitado.

    // server.js
    
    const express = require('express');
    const bodyParser = require('body-parser');
    
    const app = express();
    
    app.use(bodyParser.json());
    app.use(express.static(__dirname + '/static'));
    
    app.get('/api/contacts', (req, res) => {
      // TODO: retreive contacts and send to requester
    });
    
    app.post('/api/contacts', (req, res) => {
      const { firstName, lastName, phone } = req.body
      // TODO: create contact
    });
    
    app.delete('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      // TODO: find and delete contact by id
    });
    
    app.put('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      const { firstName, lastName, phone } = req.body
      // TODO: find and update contact by id
    });
    
    app.listen(3000, () => {
      console.log('Server is up on port 3000');
    });
    

    Una vez que se definan los modelos de datos y la estructura posterior de la base de datos SQLite, regresaré y proporcionaré la funcionalidad para las devoluciones de llamada de la ruta API.

    Sequelize ORM y Sequelize CLI

    Sequelize.js es un ORM popular para Node.js versión 4 y superior que se puede utilizar para muchos sistemas de administración de bases de datos (DBMS) diferentes, como MySQL, Postgres, SQLite y otros. Existe una biblioteca de utilidades complementaria llamada sequelize-cli que ayuda a automatizar algunas de las partes mundanas y no triviales de la programación de bases de datos.

    En este tutorial usaré sequelize-cli para encargarme de generar el lenguaje de definición de datos (DDL) para crear tablas de bases de datos, así como generar modelos de datos que crean y ejecutan el lenguaje de manipulación de datos (DML) para consultar la base de datos, e incluso un sistema de migración para ayudar con la versión que controla el esquema de la base de datos.

    Para comenzar, usaré sequelize-cli para inicializar la capa de acceso a datos del proyecto así:

    $ node_modules/.bin/sequelize init
    

    Este comando creará la configuración de directorios, migraciones, modelos y sembradoras, lo que hará que la estructura de mi proyecto se vea así:

    .
    ├── config
    │   └── config.json
    ├── migrations
    ├── models
    │   └── index.js
    ├── package-lock.json
    ├── package.json
    ├── seeders
    ├── server.js
    └── static
        └── index.html
    

    El propósito de los directorios es el siguiente:

    • config / index.js: esto define los parámetros de conexión y el dialet sql
    • migraciones: contiene scripts de migración para administrar el control de versiones del esquema
    • modelos: contiene los modelos de datos que usa para interactuar con la base de datos dentro del código de su aplicación
    • sembradoras: contiene scripts para completar su base de datos con datos iniciales

    En primer lugar, necesito editar el archivo config / config.json para que sequelize sepa que voy a trabajar con una base de datos SQLite. Así que cambiaré el archivo de esto …

    {
      "development": {
        "username": "root",
        "password": null,
        "database": "database_development",
        "host": "127.0.0.1",
        "dialect": "mysql"
      },
      "test": {
        "username": "root",
        "password": null,
        "database": "database_test",
        "host": "127.0.0.1",
        "dialect": "mysql"
      },
      "production": {
        "username": "root",
        "password": null,
        "database": "database_production",
        "host": "127.0.0.1",
        "dialect": "mysql"
      }
    }
    

    a esto …

    {
      "development": {
        "dialect": "sqlite",
        "storage": "./database.sqlite3"
      },
      "test": {
        "dialect": "sqlite",
        "storage": ":memory"
      },
      "production": {
        "dialect": "sqlite",
        "storage": "./database.sqlite3"
      }
    }
    

    que creará y utilizará un archivo de base de datos SQLite llamado database.sqlite3 en la raíz del proyecto.

    Ahora seguiré ese comando con otro, pero esta vez usaré el model:generateargumento para definir mi modelo de contacto y sus atributos, de la siguiente manera:

    $ node_modules/.bin/sequelize model:generate --name Contact --attributes firstName:string,lastName:string,phone:string,email:string
    

    El --nameparámetro es obviamente el nombre del modelo a generar y el --attibutesparámetro va seguido de los campos de objeto que lo definen junto con sus tipos de datos. Las salidas de este comando son dos archivos nuevos:

    • modelos / contact.js: un modelo de datos que se utilizará en el código lógico de la aplicación Node.js
    • migrations / aaaammddHHMMSS-create-contact.js: un script de migración que emitirá DDL SQL para crear la tabla de contactos en la base de datos

    Además de los atributos especificados en el model:generatecomando sequelize-cli también generará un número entero auto-incrementales idcampo, así como como createdAty updatedAtcampos de fecha de enlace.

    Lo siguiente que debe hacer es ejecutar la migración para que la base de datos SQLite contenga la tabla de contactos de la siguiente manera:

    $ node_modules/.bin/sequelize db:migrate
    

    Este comando indicará que la migración se ha ejecutado correctamente. Ahora puedo abrir mi archivo database.sqlite3 recién generado y ver el esquema así:

    $ sqlite3 database.sqlite3
    SQLite version 3.20.1 2017-08-24 16:21:36
    Enter ".help" for usage hints.
    sqlite> .schema
    CREATE TABLE `SequelizeMeta` (`name` VARCHAR(255) NOT NULL UNIQUE PRIMARY KEY);
    CREATE TABLE `Contacts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `firstName` VARCHAR(255), `lastName` VARCHAR(255), `phone` VARCHAR(255), `email` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
    CREATE TABLE sqlite_sequence(name,seq);
    

    Tenga en cuenta que también hay otra tabla allí llamada SequelizeMeta. Esta es la tabla que utiliza Sequelize.js para mantener el orden en que se ejecutan las migraciones. Por ejemplo, al ejecutar este comando, se mostrará el nombre del script de migración que acabo de ejecutar.

    sqlite> .headers ON
    sqlite> select * from SequelizeMeta;
    name
    20180726180039-create-contact.js
    

    Ahora que tengo el modelo de contacto asignado a una tabla de la base de datos, me gustaría generar un script de sembrador para completar previamente mi base de datos con algunos datos para demostrar cómo interactuar con el ORM en el código de mi aplicación. La generación de un script de sembradora es muy similar a los comandos anteriores.

    $ node_modules/.bin/sequelize seed:generate --name seed-contact
    

    El resultado es un nuevo script en el directorio de sembradoras de la convención de nomenclatura aaaammddHHMMSS-seed-contact.js. Inicialmente es solo un andamio de una interfaz de sembradora como esta:

    'use strict';
    
    module.exports = {
      up: (queryInterface, Sequelize) => {
        /*
          Add altering commands here.
          Return a promise to correctly handle asynchronicity.
    
          Example:
          return queryInterface.bulkInsert('Person', [{
            name: 'John Doe',
            isBetaMember: false
          }], {});
        */
      },
    
      down: (queryInterface, Sequelize) => {
        /*
          Add reverting commands here.
          Return a promise to correctly handle asynchronicity.
    
          Example:
          return queryInterface.bulkDelete('Person', null, {});
        */
      }
    };
    

    Lo editaré de la siguiente manera para agregar algunos contactos.

    'use strict';
    
    module.exports = {
      up: (queryInterface, Sequelize) => {
        /*
          Add altering commands here.
          Return a promise to correctly handle asynchronicity.
    
          Example:
          return queryInterface.bulkInsert('Person', [{
            name: 'John Doe',
            isBetaMember: false
          }], {});
        */
       return queryInterface.bulkInsert('Contacts', [{
          firstName: 'Snoop',
          lastName: 'Dog',
          phone: '111-222-3333',
          email: '[email protected]',
          createdAt: new Date().toDateString(),
          updatedAt: new Date().toDateString()
        }, {
          firstName: 'Scooby',
          lastName: 'Doo',
          phone: '444-555-6666',
          email: '[email protected]',
          createdAt: new Date().toDateString(),
          updatedAt: new Date().toDateString()
        }, {
          firstName: 'Herbie',
          lastName: 'Husker',
          phone: '402-437-0001',
          email: '[email protected]',
          createdAt: new Date().toDateString(),
          updatedAt: new Date().toDateString()
        }], {});
      },
    
      down: (queryInterface, Sequelize) => {
        /*
          Add reverting commands here.
          Return a promise to correctly handle asynchronicity.
    
          Example:
          return queryInterface.bulkDelete('Person', null, {});
        */
       return queryInterface.bulkDelete('Contacts', null, {});
      }
    };
    

    Por último, necesito ejecutar la sembradora para completar la base de datos con estos contactos iniciales.

    $ node_modules/.bin/sequelize db:seed:all
    

    Lo que me da un resultado en la consola que me permite saber que la tabla de la base de datos se sembró correctamente con datos.

    Ok, la configuración finalmente está lista. Ahora puedo pasar a la parte más divertida de interactuar realmente con mi modelo de contacto respaldado por la base de datos y darles a los puntos finales API previamente eliminados la funcionalidad requerida.

    Sin embargo, antes de poder usar mi modelo de contacto, necesito informarle a la aplicación Express.js que existe. Hago esto agregando un requireen server.js y asignándolo a una constvariable llamada dbasí:

    // server.js
    const express = require('express');
    const bodyParser = require('body-parser');
    const db = require('./models'); // new require for db object
    

    Esta dbvariable contendrá mi modelo de contacto, al que se puede acceder mediante db.Contact.

    Empiezo con el punto final más simple de la API, el GET /api/contactspunto final. Este punto final simplemente necesita tener todos los contactos devueltos de la base de datos y serializados en una respuesta al solicitante que llama. Puedo llamar al findAllmétodo del Contactobjeto y una vez que se devuelve la promesa thenable puedo enviar los contactos al cliente utilizando el familiar res.send(...)proporcionado por el marco Express.js.

    // server.js
    // ommitting everything above ...
    
    app.get('/api/contacts', (req, res) => {
      return db.Contact.findAll()
        .then((contacts) => res.send(contacts))
        .catch((err) => {
          console.log('There was an error querying contacts', JSON.stringify(err))
          return res.send(err)
        });
    });
    
    // omitting everything below...
    

    Ahora puedo iniciar mi servidor Express, apuntar mi navegador a localhost: 3000 / index.html, y seré recibido con la IU de mis contactos.

    $ nodemon
    

    … o si no usa nodemon:

    $ npm start
    

    Continuando, ahora implementaré la funcionalidad de creación para el POST /api/contactspunto final. Crear nuevas instancias de contactos es muy sencillo. Simplemente llame al create(...)método del db.Contactobjeto, espere a que se resuelva la promesa encadenando a then(...)y luego devolveré el contacto recién creado al cliente, así:

    // server.js
    // ommitting everything above ...
    
    app.post('/api/contacts', (req, res) => {
      const { firstName, lastName, phone } = req.body
      return db.Contact.create({ firstName, lastName, phone })
        .then((contact) => res.send(contact))
        .catch((err) => {
          console.log('***There was an error creating a contact', JSON.stringify(contact))
          return res.status(400).send(err)
        })
    });
    
    // omitting everything below ...
    

    Ahora, si ingreso otro contacto, digamos, Wonder Woman, pero dejo el número de teléfono en blanco (ella dijo que me llamaría en lugar de dar su número), en el formulario “Agregar contacto” de la interfaz de usuario y presionar “Guardar”, mi lista de contactos aparecerá ahora tiene cuatro miembros: Snoop Dog, Scooby Doo, Herbie Husker y Wonder Woman.

    Continuando, ahora puedo agregar la funcionalidad para implementar el comportamiento de actualización para el PUT /api/contacts/:idpunto final. Esta es una interacción de dos partes desde la perspectiva de la programación de la base de datos.

    Primero empiezo por encontrar el contacto que coincida con la ID dada en la URL de la API usando el db.Contact.findById(...)método, luego modifico los campos con nuevos datos y termino usando el update(...)método del contactobjeto que envía el modelo actualizado a la base de datos donde esos cambios serán ser persistido.

    // server.js
    // ommitting everything above ...
    
    app.put('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      return db.Contact.findById(id)
      .then((contact) => {
        const { firstName, lastName, phone } = req.body
        return contact.update({ firstName, lastName, phone })
          .then(() => res.send(contact))
          .catch((err) => {
            console.log('***Error updating contact', JSON.stringify(err))
            res.status(400).send(err)
          })
      })
    });
    
    // omitting everything below...
    

    Con esto en su lugar, ahora puedo actualizar el número de teléfono de Wonder Woman porque mi buen amigo Snoop Dog amablemente me lo dio. Para hacer esto, hago clic en el nombre de Wonder Woman en la lista de contactos de la interfaz de usuario para expandir el formulario de detalles / actualización. Después de completar su número 111-888-1213 y hacer clic en Guardar, mi contacto de Wonder Woman se actualiza.

    Lo último que debe hacer es agregar la capacidad de eliminar un contacto a través del DELETE /api/contacts/:idpunto final. La eliminación es muy similar a la funcionalidad de actualización en el sentido de que la instancia del modelo que desea eliminar primero debe recuperarse de la base de datos y luego simplemente debe llamar al destroy()método de la instancia del modelo.

    // server.js
    // ommitting everything above ...
    
    app.delete('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      return db.Contact.findById(id)
        .then((contact) => contact.destroy())
        .then(() => res.send({ id }))
        .catch((err) => {
          console.log('***Error deleting contact', JSON.stringify(err))
          res.status(400).send(err)
        })
    });
    
    // omitting everything below ...
    

    Resulta que es bueno que los contactos de mi lista de contactos se puedan eliminar porque Wonder Woman resultó ser una verdadera reina del drama y estaba buscando peleas con mi esposa, no genial Wonder Woman. Estás a punto de ser eliminado.

    Desde la interfaz de usuario puedo eliminar Wonder Woman haciendo clic en la papelera a la derecha de su nombre.

    La totalidad del script server.js se muestra a continuación para que esté completo.

    // server.js
    
    // server.js
    
    const express = require('express');
    const bodyParser = require('body-parser');
    const db = require('./models');
    
    const app = express();
    
    app.use(bodyParser.json());
    app.use(express.static(__dirname + '/static'));
    
    app.get('/api/contacts', (req, res) => {
      return db.Contact.findAll()
        .then((contacts) => res.send(contacts))
        .catch((err) => {
          console.log('There was an error querying contacts', JSON.stringify(err))
          return res.send(err)
        });
    });
    
    app.post('/api/contacts', (req, res) => {
      const { firstName, lastName, phone } = req.body
      return db.Contact.create({ firstName, lastName, phone })
        .then((contact) => res.send(contact))
        .catch((err) => {
          console.log('***There was an error creating a contact', JSON.stringify(contact))
          return res.status(400).send(err)
        })
    });
    
    app.delete('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      return db.Contact.findById(id)
        .then((contact) => contact.destroy({ force: true }))
        .then(() => res.send({ id }))
        .catch((err) => {
          console.log('***Error deleting contact', JSON.stringify(err))
          res.status(400).send(err)
        })
    });
    
    app.put('/api/contacts/:id', (req, res) => {
      const id = parseInt(req.params.id)
      return db.Contact.findById(id)
      .then((contact) => {
        const { firstName, lastName, phone } = req.body
        return contact.update({ firstName, lastName, phone })
          .then(() => res.send(contact))
          .catch((err) => {
            console.log('***Error updating contact', JSON.stringify(err))
            res.status(400).send(err)
          })
      })
    });
    
    app.listen(3000, () => {
      console.log('Server is up on port 3000');
    });
    

    Conclusión

    En este tutorial he demostrado cómo usar el ORM de Sequelize.js junto con su CLI de Sequelize para manejar la programación de la base de datos junto con SQLite. Al hacerlo, proporcioné una aplicación de lista de contactos tonta que utiliza Node.js / Express.js para un servidor de aplicaciones junto con una interfaz de usuario basada en Vue.js. Sequelize.js es un ORM bastante útil que elimina muchos de los detalles necesarios para la programación de bases de datos dentro de las aplicaciones basadas en Node.js y es bastante popular entre los desarrolladores de Node.js.

    Como siempre, les agradezco su lectura y agradezco los comentarios y críticas a continuación.

     

    Etiquetas:

    Deja una respuesta

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