Using Sequelize with SvelteKit
As you might have read in my previous story, Transitioning from Vue to Svelte?, I'm currently testing the Svelte / SvelteKit combo. But how can I integrate Sequelize with this?
I was looking for a tutorial on how to use Sequelize with SvelteKit, but I was a bit disappointed to not find any. It took me the whole evening yesterday to figure out how to plug Sequelize into SvelteKit, especially as I had never used a +page.server.js file before.
I kind of fumbled around with +hook.server.js, then tried to set up an /api/+server.js page, then had trouble with require() not being available in SvelteKit...
In the end, the solution is very simple. So I'll make this small tutorial, in case it helps any SvelteKit beginner like me to not loose too much time.
Creating the Sequelize models
The first thing we'll do is create the Sequelize models. My main problem is that much of the Sequelize code we see on the web uses require(), which tends to be less and less available in modern javascript.
Here is how to create your Sequelize models without needing any require().
// /src/lib/server/models/Story.js
const defineStory = function(sequelize, DataTypes) {
return sequelize.define('Story', {
cid: { type: DataTypes.STRING(250), allowNull: false, primaryKey: true },
title: { type: DataTypes.STRING(250) },
html: { type: DataTypes.TEXT },
write_date: { type: DataTypes.DATE },
tags: { type: DataTypes.STRING(250) }
},
{
sequelize,
tableName: 'stories',
timestamps: false,
indexes: [
{
name: "idx_write_date",
unique: false,
using: "BTREE",
fields: [
{ name: "write_date" },
]
},
]
})
}
export { defineStory }
Simple file. Just one function that will generate the Story model, and we export that function to be able to use it later.
This Story model corresponds to the table where are stored all StoryPress stories. One row corresponds to one IPFS file. Yes, the stories are primarily on IPFS, but that Inter-Planetary File System is sometimes so slow (as if some files were actually written on Ceres), that StoryPress also stores them in a local database, that you can consider as an IPFS local cache.
Now, let's create a second data model:
// /src/lib/server/models/StoryList.js
const defineStoryList = function(sequelize, DataTypes) {
return sequelize.define('Story_list', {
story_author: { type: DataTypes.CHAR(42), allowNull: false, primaryKey: true },
story_id: { type: DataTypes.INTEGER, allowNull: false, primaryKey: true },
earned: { type: DataTypes.INTEGER, allowNull: false, default: 0 },
cid: { type: DataTypes.STRING(250), allowNull: false },
block_id: { type: DataTypes.INTEGER, allowNull: false },
},
{
sequelize,
tableName: 'story_list',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "story_author" },
{ name: "story_id" }
]
},
]
})
}
export { defineStoryList }
The story_list table stores all stories that are written on the Fantom blockchain. A story is uniquely defined by the author, and an id. That is why story_author and story_id are used to make the composite primary key of that table.
This table gets populated by an independent script that listens to blockchain events. But that is a topic for another tutorial.
StoryPress has many more models, but for that simple tutorial, we'll just keep it here.
Instanciate sequelize and models
Now that we have our model definitions ready, we create a db.js file that will instanciate our sequelize connection object, and the two models we have just defined:
// /src/lib/server/db.js
import { Sequelize, DataTypes } from 'sequelize'
import { defineStory } from './models/Story'
import { defineStoryList } from './models/StoryList'
// we create the db cnx
const sequelize = new Sequelize('postgres://[user]:[pwd]@localhost:5434/cherrific', {
logging: false,
pool: { max: 10, min: 5, idle: 30000, acquire: 60000 }
})
// we instantiate our models
const Story = defineStory(sequelize, DataTypes)
const StoryList = defineStoryList(sequelize, DataTypes)
// relations between models
StoryList.hasOne(Story, { foreignKey: "cid" })
Story.belongsTo(StoryList, { foreignKey: "cid" })
export { sequelize, Story, StoryList }
We now have or connection object, and our two models, ready to be used in our SvelteKit application.
Creating the endpoint to serve data to our pages
This is where SvelteKit shines. Here, we want to fetch data from our db for the home page of our site. All we need to do is create a +page.server.js file, that will make this data available:
// /src/routes/+page.server.js
import { sequelize, StoryList } from '$lib/server/db'
/** @type {import('./$types').PageServerLoad} */
export async function load() {
// Cherrific stats go there
const stats = {}
// get number of stories
stats.stories = (await sequelize.query('select count(*) as nb from story_list'))[0][0].nb
// list of stories
const stories = await StoryList.findAll()
// purify data
const clean = stories.map((s) => JSON.parse(JSON.stringify(s)))
return { stats, stories: clean }
}
I did not import the Story model, because I don't need it in that page.
I have written two examples of database queries. The first uses the sequelize connection object to send a raw query. The second gets all the available stories. I've just kept these examples simple because this is not a Sequelize tutorial.
You might get a bit puzzled by the [0][0].nb bit. sequelize.query() returns an array of two elements: [results, metadata]. As I am only interested in the results, I only take the first element. Hence the first [0].
Now, I only want the first element of that results array. Hence the second [0]. This is the first raw returned by my database. As you can see from the query, it has only one property: nb.
The purifying line is another kind of weirdness. StoryList.findAll() will return me an array of Sequelize row objects. We are actually only interested in the data. So we map over each of these over complicated objects, turn them to json to get rid of the Sequelize fancy stuff, then turn them back into simpler objects with JSON.parse().
Maybe there is a more clever way to do this, but that is all I could manage to find.
Displaying the data to visitors
The last part is to finally render that data to our visitors via the +page.svelte file:
// /src/routes/+page.svelte
<script>
/** @type {import('./$types').PageData} */
export let data
</script>
<p>Number of stories: { data.stats.stories }</p>
<p>CID of first story: { data.stories[0].cid }</p>
I've kept the example as simple as possible, but you will probably want to display the list of all the stories we have with a {#each } block.
That's it. That's how you can easily use Sequelize in your SvelteKit applications.
I've been a huge fan of the Vue + Nuxt combo for a few years now, because I find Vue much simpler than React.
But in term of simplicity, the Svelte + SvelteKit combo is many steps further than Vue + Nuxt. There is no boilerplate code. I didn't even have to set some Express server up to handle the API side. Svelte and SvelteKit are such time savers!!!
I hope you found this tutorial useful. In case there is something I've missed, or could have done better, please tell me in the comments. If that tutorial helped you, please consider leaving me a like. ;-)