Let's create a new file, models/notes-sequelize.mjs:
import fs from 'fs-extra';
import util from 'util';
import jsyaml from 'js-yaml';
import Note from './Note';
import Sequelize from 'sequelize';
import DBG from 'debug';
const debug = DBG('notes:notes-sequelize');
const error = DBG('notes:error-sequelize');
var SQNote;
var sequlz;
async function connectDB() {
if (typeof sequlz === 'undefined') {
const YAML = await fs.readFile(process.env.SEQUELIZE_CONNECT,'utf8');
const params = jsyaml.safeLoad(YAML, 'utf8');
sequlz = new Sequelize(params.dbname, params.username,
params.password, params.params);
}
if (SQNote) return SQNote.sync();
SQNote = sequlz.define('Note', {
notekey: { type: Sequelize.STRING, primaryKey: true, unique:
true },
title: Sequelize.STRING,
body: Sequelize.TEXT
});
return SQNote.sync();
}
The database connection is stored in the sequlz object, and is established by reading a configuration file (we'll go over this file later), and instantiating a Sequelize instance. The data model, SQNote, describes our object structure to Sequelize so that it can define corresponding database table(s). If SQNote is already defined, we simply return it, otherwise we define and return SQNote.
The Sequelize connection parameters are stored in a YAML file we specify in the SEQUELIZE_CONNECT environment variable. The line new Sequelize(..) opens the database connection. The parameters obviously contain any needed database name, username, password, and other options required to connect with the database.
The line sequlz.define is where we define the database schema. Instead of defining the schema as the SQL command to create the database table, we're giving a high-level description of the fields and their characteristics. Sequelize maps the object attributes into columns in tables.
We're telling Sequelize to call this schema Note, but we're using a SQNote variable to refer to that schema. That's because we already defined Note as a class to represent notes. To avoid a clash of names, we'll keep using the Note class, and use SQNote to interact with Sequelize about the notes stored in the database.
Sequelize class: http://docs.sequelizejs.com/en/latest/api/sequelize/.
Defining models: http://docs.sequelizejs.com/en/latest/api/model/.
Add these functions to models/notes-sequelize.mjs:
export async function create(key, title, body) {
const SQNote = await connectDB();
const note = new Note(key, title, body);
await SQNote.create({ notekey: key, title: title, body: body });
return note;
}
export async function update(key, title, body) {
const SQNote = await connectDB();
const note = await SQNote.find({ where: { notekey: key } })
if (!note) { throw new Error(`No note found for ${key}`); } else {
await note.updateAttributes({ title: title, body: body });
return new Note(key, title, body);
}
}
There are several ways to create a new object instance in Sequelize. The simplest is to call an object's create function (in this case, SQNote.create). That function collapses together two other functions, build (to create the object), and save (to write it to the database).
Updating an object instance is a little different. First, we must retrieve its entry from the database using the find operation. The find operation is given an object specifying the query. Using find, we retrieve one instance, whereas the findAll operation retrieves all matching instances.
For documentation on Sequelize queries, visit http://docs.sequelizejs.com/en/latest/docs/querying/.
Like most or all other Sequelize functions, SQNote.find returns a Promise. Therefore, inside an async function, we await the result of the operation.
The update operation requires two steps, the first being to find the corresponding object to read it in from the database. Once the instance is found, we can update its values simply with the updateAttributes function:
export async function read(key) {
const SQNote = await connectDB();
const note = await SQNote.find({ where: { notekey: key } })
if (!note) { throw new Error(`No note found for ${key}`); } else {
return new Note(note.notekey, note.title, note.body);
}
}
To read a note, we use the find operation again. There is the possibility of an empty result, and we have to throw an error to match.
The contract for this function is to return a Note object. That means taking the fields retrieved using Sequelize and using that to create a Note object:
export async function destroy(key) {
const SQNote = await connectDB();
const note = await SQNote.find({ where: { notekey: key } })
return note.destroy();
}
To destroy a note, we use the find operation to retrieve its instance, and then call its destroy() method:
export async function keylist() {
const SQNote = await connectDB();
const notes = await SQNote.findAll({ attributes: [ 'notekey' ] });
return notes.map(note => note.notekey);
}
Because the keylist function acts on all Note objects, we use the findAll operation. We query for the notekey attribute on all notes. We're given an array of objects with a field named notekey, and we use the .map function to convert this into an array of the note keys:
export async function count() {
const SQNote = await connectDB();
const count = await SQNote.count();
return count;
}
export async function close() {
if (sequlz) sequlz.close();
sequlz = undefined;
SQNote = undefined;
}
For the count function, we can just use the count() method to calculate the needed result.