Collaborative Drawing App: Using the Database

Shawn
3 min readJun 4, 2022

--

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.

An example of the final product

Here’s the final code for the project. See if you can modify it to do cool, new things.

Collaborative Drawing App Series

--

--