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 *