In this section we set up Sequelize and Express, then add a save button to store canvas drawings in the database.
Setting up the Database and Sequelize Model
We have just one table for our project, called Room. In more complex projects we might define these in different files, but for a simple project like this, it’s sufficient to just use index.js
.
const {Sequelize} = require('sequelize');const db = new Sequelize({
dialect: 'sqlite',
storage: __dirname + '/collab.sqlite'
});
const Room = db.define('Room', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: Sequelize.STRING,
image: Sequelize.STRING
}, {
freezeTableName: true,
timestamps: false
});
This code imports Sequelize, sets up the connection to our SQLite database, and defines the Room model. The name
field will correspond to the name in the URL on the front end. The image
field is the text representation of the canvas image.
Now that we have the model defined, we can use it in our Express routes. There are only 2 routes really needed. One to retrieve the image based on the room name, and one that either inserts or updates. Insert/update are combined in the second route to make things easier on the frontend. It lets us have just a Save button that makes a singular call.
app.get('/api/room/:name', async (req, res) => {
const {name} = req.params;
const result = await Room.findOne({where: {name}});
res.send({result});
});app.post('/api/room/:name', async (req, res) => {
const {name} = req.params;
const {image} = req.body;
const result = await Room.findOne({where: {name}});
if (result) {
// update
result.image = image;
await result.save();
res.send({result});
} else {
// insert
const newImage = await Room.create({name, image});
res.send({result: newImage});
}
});
Because we already set up the database, there’s nothing more that needs doing on the backend. On to the frontend.
Retrieving the Image
We want to retrieve the image stored in the database on page load, and immediately display it on the canvas. The method goes on the Canvas
class. The way it works is a little unusual:
async getImage() {
const result = await fetch(`http://localhost:3000/api/room/${this.room.room}`);
const image = await result.json();
if (image.result) {
const img = new Image();
img.onload = e => {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawImage(img, 0, 0, img.width, img.height);
}
img.setAttribute('src', image.result.image);
}
}
The method first checks the database for a stored image with this name, if any exists. If so, creates a new Image
object, a built-in Javascript object that essentially makes an <img> tag. We set its src
attribute to the image we just created. Recall that it’ll be one of those long data URL strings. It can take the image a moment to load depending on how large it is, so we have the onload
event handler. Once it’s loaded, we clear out the canvas and draw the image onto it.
To call this function, add this line in index.html:
canvas.getImage();
Saving/Updating the Image
Back in the Canvas
class we add the method to save/update the image:
async save() {
const image = this.canvas.toDataURL();
await fetch(`http://localhost:3000/api/session/${this.room.room}`, {
method: 'POST',
body: JSON.stringify({image}),
headers: {
'Content-Type': 'application/json'
}
});
}
At the start we convert the canvas image to a data URL. This’ll be that long string representation. We also specify headers to be application/json
. This is to tell the backend it is indeed receiving a JSON object.
Now in the index.html file, we add the button next to the Clear and Download buttons:
<button onclick="canvas.save()">Save</button>
Putting it Together
To test this I would recommend choosing a memorable name. For example, we could use http://localhost:3000/foo
. Like before, the tabs will update with each other, and now we can hit save. When we refresh the tab the image should load. We can make further changes and save, and they’ll appear on refreshes.
Here’s the final code for the project. See if you can modify it to do cool, new things.