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

En este tutorial, demostraré cómo crear una aplicación web de administración de contactos simple usando Node.js, Express.js, Vue.js junto con el seq...

En este tutorial, demostraré cómo crear una aplicación web de administración de contactos simple usando Nodo.js, Express.js , Vue.js junto con el mapeador relacional de objetos (ORM) secuela.js respaldado por un SQLite base de datos.

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](/un-tutorial-de-sqlite-con -nodo-js/). Puedes encontrar el código completo de este tutorial en mi cuenta de GitHub.

Configuración e instalación {#configuración e instalación}

Para comenzar, inicializaré un nuevo proyecto usando el buen ole npm init y presionando enter para aceptar todos los valores predeterminados, excepto 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.

1
2
$ 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 tener que reiniciar Node.js todo el tiempo.

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

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

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

1
$ 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 Electrón), 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 y la 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 las llamadas AJAX más adelante. .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 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>

Ok, 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 los contactos, obviamente necesitaré alguna representación de una persona, a la que me referiré como un objeto de contacto. En cuanto a los comportamientos, probablemente necesite lo siguiente:

  • Ver la lista de todos los contactos
  • Añadir contactos
  • Ver detalles de un contacto
  • Actualizar 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 REST de AJAX, así que permítanme diseñar los puntos finales de la API que creo que necesitaré.

Funcionalidad del método de ruta


/api/contacts GET Recuperar todos los contactos /api/contacts POST Crear contacto /api/contacts/:id PUT Actualizar los detalles de un contacto /api/contacts/:id DELETE Eliminar 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 AddUpdateContact que puedo usar para agregar nuevos contactos a la aplicación o actualizar los existentes. Entonces, en la parte inferior de la etiqueta cuerpo del HTML, agrego una etiqueta script y el siguiente código JavaScript basado en Vue.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<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">
        <h3 class='subtitle'>{{ title }}</h3>
        <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 componente AddUpdateContact puede tener dos accesorios: (1) un título y, (2) en el caso opcional en el que se usará para actualizar un contacto existente, un objeto de contacto. Los miembros de “datos” se inicializan en función de si el objeto “contacto” se pasa como accesorio o no. La sección plantilla contiene un formulario y campos de entrada para agregar nueva información de contacto o modificar una existente. Luego, la sección de métodos contiene un solo método save que emite la información del contacto hasta un componente principal, lo cual es consistente con la [Filosofía de enlace de datos unidireccional de Vue.js](https://vuejs.org/ v2/guide/components.html#Sending-Messages-to-Parents-with-Events).

A continuación, crearé un componente Contacto dentro de la etiqueta script, que se verá así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<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 componente Contacto recibirá un objeto contacto como accesorio que contiene los datos que se mostrarán en su plantilla. El componente ‘Contacto’ también utilizará el componente ‘AddUpdateContact’ descrito anteriormente para mostrar los detalles del contacto, así como para brindarle al usuario la capacidad de actualizarlo.

La funcionalidad de actualización es posible mediante el uso del método del controlador de eventos onAddOrUpdateContact que escucha el evento save-contact del componente AddUpdateContact, 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 método deleteContact del componente Contact que también propaga el mensaje de eliminación hasta la instancia principal de Vue.js.

Ok, para terminar el código JavaScript frontend, necesito instanciar el objeto raíz Vue.js y decirle que se vincule a un elemento div con id de "app" así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<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 que se debe cuidar es el HTML necesario para representar los componentes de Vue.js, que se muestra a continuación en la totalidad del archivo index.html.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<!-- 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">
        <h3 class='subtitle'>{{ title }}</h3>
        <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

Como estoy utilizando Express.js para esta aplicación, necesitaré crear un archivo JavaScript llamado server.js que sirva 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, introduzco el marco Express.js a través de require y 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 "static". En la parte inferior de la secuencia de comandos server.js, le digo al servidor de aplicaciones que enlace (escuche) el puerto 3000 y las solicitudes del servidor.

Para realizar el andamiaje de los puntos finales de la API REST definidos previamente en la tabla de la sección anterior, necesito un analizador de cuerpo e indico a la aplicación express que se usará para analizar el contenido del cuerpo HTTP. Luego aplico los extremos de la API que sirven el contenido solicitado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 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 de la base de datos SQLite posterior, 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 usar 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 creen y ejecuten 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 de la siguiente manera:

1
$ node_modules/.bin/sequelize init

Este comando creará la configuración de los directorios, las migraciones, los modelos y las semillas, lo que dará como resultado que la estructura de mi proyecto se vea así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
.
├── 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 utiliza para interactuar con la base de datos dentro del código de su aplicación
  • seeders - contiene scripts para llenar 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 voy a cambiar el archivo de este...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "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 ...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "development": {
    "dialect": "sqlite",
    "storage": "./database.sqlite3"
  },
  "test": {
    "dialect": "sqlite",
    "storage": ":memory"
  },
  "production": {
    "dialect": "sqlite",
    "storage": "./database.sqlite3"
  }
}

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

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

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

El parámetro --name es obviamente el nombre del modelo a generar y el parámetro --attibutes es seguido por los campos de objeto que lo definen junto con sus tipos de datos. Los resultados de este comando son dos archivos nuevos:

  1. models/contact.js: un modelo de datos que se usará en el código lógico de la aplicación Node.js 2.migrations/yyyymmddHHMMSS-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 comando model:generate, sequelize-cli también generará un campo id de entero incrementado automáticamente, así como campos de enlace de fecha createdAt y updatedAt.

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:

1
$ node_modules/.bin/sequelize db:migrate

Este comando indicará que la migración se ha ejecutado correctamente. Ahora puedo abrir mi archivo base de datos.sqlite3 recién generado y ver el esquema de esta manera:

1
2
3
4
5
6
7
$ 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 llamada SequelizeMeta. Esta es la tabla que usa Sequelize.js para controlar el orden en que se ejecutan las migraciones. Por ejemplo, ejecutar este comando mostrará el nombre del script de migración que acabo de ejecutar.

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

Ahora que tengo el modelo de contacto asignado a una tabla de 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 seeder es muy similar a los comandos anteriores.

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
'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: '[correo electrónico protegido]',
      createdAt: new Date().toDateString(),
      updatedAt: new Date().toDateString()
    }, {
      firstName: 'Scooby',
      lastName: 'Doo',
      phone: '444-555-6666',
      email: '[correo electrónico protegido]',
      createdAt: new Date().toDateString(),
      updatedAt: new Date().toDateString()
    }, {
      firstName: 'Herbie',
      lastName: 'Husker',
      phone: '402-437-0001',
      email: '[correo electrónico protegido]',
      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 llenar la base de datos con estos contactos iniciales.

1
$ 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 proporcionar la funcionalidad requerida a los puntos finales de la API previamente desconectados.

Sin embargo, antes de poder usar mi modelo de contacto, debo informar a la aplicación Express.js que existe. Hago esto agregando un require en server.js y asignándolo a una variable const llamada db así:

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

Esta variable db contendrá mi modelo de contacto, al que se puede acceder a través de db.Contact.

Comienzo con el punto final más simple de la API, el punto final GET /api/contacts. Este punto final simplemente necesita que todos los contactos sean devueltos desde la base de datos y serializados en una respuesta al solicitante que llama. Puedo llamar al método ‘findAll’ del objeto ‘Contacto’ y una vez que se devuelve la promesa entonces puedo enviar los contactos al cliente usando el conocido ‘res.send(…)’ proporcionado por el marco Express.js .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 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 interfaz de usuario de mis contactos.

1
$ nodemon

... o si no usa nodemon:

1
$ npm start

Captura de pantalla de la aplicación de contacto{.img-responsive}

Continuando, ahora implementaré la funcionalidad de creación para el punto final POST /api/contacts. Crear nuevas instancias de contacto es súper simple. Simplemente llame al método create(...) del objeto db.Contact, espere a que la promesa se resuelva encadenando then(...) y luego devolveré el contacto recién creado al cliente, así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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 presiono " Guardar" mi lista de contactos ahora tendrá 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 punto final PUT /api/contacts/:id. 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 método db.Contact.findById(...), luego modifico los campos con nuevos datos y termino usando update(...) .) del objeto contacto que envía el modelo actualizado de regreso a la base de datos donde se conservarán esos cambios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 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 los detalles/formulario de 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 punto final DELETE /api/contacts/:id. 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 llamar al método destroy() de la instancia del modelo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 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 la Mujer Maravilla resultó ser una verdadera reina del drama y estaba peleando con mi esposa, no con la Mujer Maravilla genial. Estás a punto de ser eliminado.

Desde la interfaz de usuario, puedo eliminar a 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 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 Sequelize.js junto con su Sequelize CLI para manejar la programación de bases 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.