Skip to content

Commit

Permalink
doc/website/blog: announcing external loader for sequelize post
Browse files Browse the repository at this point in the history
  • Loading branch information
ronenlu committed Sep 20, 2023
1 parent 160fc1d commit 21aaa83
Show file tree
Hide file tree
Showing 2 changed files with 386 additions and 0 deletions.
381 changes: 381 additions & 0 deletions doc/website/blog/2023-09-25-sequelize-schema-loader.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
---
title: "Announcing External Loader for Sequelize Schemas"
authors: ronenlu
tags: [sequelize, schema, migration]
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

:::info TL;DR

You can now import the desired database schema from Sequelize project into Atlas,
and use it to automatically plan migrations for you.

[See an example](#demo-time)

:::

## Introduction

[Sequelize](https://sequelize.org/) is one of the most popular ORMs for Node.js. It supports a variety of databases, including MySQL, PostgreSQL, SQLite, and Microsoft SQL Server.

[Atlas](https://atlasgo.io) is a modern tool for managing your database schema. It allows you to inspect, plan, lint and execute schema changes
to your database. It is designed to be used by developers, DBAs and DevOps engineers alike.

Atlas supports many ways to describe database schemas. Using [Schema Loaders](https://atlasgo.io/blog/2023/01/05/atlas-v090#schema-loaders),
plain SQL, a connection to another database or using [Atlas HCL](https://atlasgo.io/atlas-schema/sql-resources).

Today, I'm happy to announce that Atlas now supports loading the schema state from Sequelize Projects.

## Demo Time

Sequelize allows users to
manage their database schemas using its [sync](https://sequelize.org/docs/v6/core-concepts/model-basics/#model-synchronization/) feature,
which is usually sufficient during development and in many simple cases.

However, at some point, teams need more control and decide to employ
the [migrations](https://sequelize.org/master/manual/migrations.html)
methodology, which is a more robust way to manage your database schema.
The problem with [creating migrations](https://sequelize.org/docs/v6/other-topics/migrations/#creating-the-first-model-and-migration)
in Sequelize is that they are usually written by hand, which is error-prone and
time-consuming.

Atlas can automatically plan database schema migrations for developers using Sequelize.
Atlas plans migrations by calculating the diff between the _current_ state of the database,
and its _desired_ state.

### Installation

If you haven't already, install Atlas from macOS or Linux by running:
```bash
curl -sSf https://atlasgo.sh | sh
```
See [atlasgo.io](https://atlasgo.io/getting-started#installation) for more installation options.

Install the [Atlas Sequelize Provider](https://github.com/ariga/atlas-provider-sequelize) by running:

<Tabs>
<TabItem value="js" label="JavaScript" default>

```bash
npm install @ariga/atlas-provider-sequelize
```
</TabItem>
<TabItem value="ts" label="TypeScript">

```bash
npm install @ariga/ts-atlas-provider-sequelize
```
</TabItem>
</Tabs>

Make sure all your Node dependencies are installed by running:
```bash
npm install
```

### Standalone vs Script mode

The provider can be used in two modes:
* **Standalone** - If all of your Sequelize models exist in a single Node module,
you can use the provider directly to load your Sequelize schema into Atlas.
* **Script** - In other cases, you can use the provider as an npm package to write a script
that loads your Sequelize schema into Atlas.

### Standalone mode

In your project directory, create a new file named `atlas.hcl` with the following contents:

<Tabs>
<TabItem value="js" label="JavaScript" default>

```hcl
data "external_schema" "sequelize" {
program = [
"npx",
"@ariga/atlas-provider-sequelize",
"load",
"--path", "./path/to/models",
"--dialect", "mysql", // mariadb | postgres | sqlite | mssql
]
}
env "sequelize" {
src = data.external_schema.sequelize.url
dev = "docker://mysql/8/dev"
migration {
dir = "file://migrations"
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
```
</TabItem>
<TabItem value="ts" label="TypeScript">

```hcl
data "external_schema" "sequelize" {
program = [
"npx",
"@ariga/ts-atlas-provider-sequelize",
"load",
"--path", "./path/to/models",
"--dialect", "mysql", // mariadb | postgres | sqlite | mssql
]
}
env "sequelize" {
src = data.external_schema.sequelize.url
dev = "docker://mysql/8/dev"
migration {
dir = "file://migrations"
}
format {
migrate {
diff = "{{ sql . \" \" }}"
}
}
}
```
</TabItem>
</Tabs>

For the sake of brevity, we will not review the *Script mode* in this post, but you can find more information about
it in the [Sequelize Guide](/guides/orms/sequelize).

### Load Sequelize Schema in action

Atlas supports a [versioned migrations](/concepts/declarative-vs-versioned#versioned-migrations)
workflow, where each change to the database is versioned and recorded in a migration file. You can use the
`atlas migrate diff` command to automatically generate a migration file that will migrate the database
from its latest revision to the current Sequelize schema.

Suppose we have the following Sequelize `models` directory, with two models `task` and `user`:

<Tabs>
<TabItem value="js" label="JavaScript" default>
<Tabs>
<TabItem value="task" label="task.js" default>

```js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Task = sequelize.define('Task', {
complete: {
type: DataTypes.BOOLEAN,
defaultValue: false,
}
});

Task.associate = (models) => {
Task.belongsTo(models.User, {
foreignKey: {
name: 'userID',
allowNull: false
},
as: 'tasks'
});
};

return Task;
};
```
</TabItem>
<TabItem value="user" label="user.js">

```js
'use strict';
module.exports = function(sequelize, DataTypes) {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
},
}
});

User.associate = (models) => {
User.hasMany(models.Task, {
foreignKey: {
name: 'userID',
allowNull: false
},
as: 'tasks'
});
};

return User;
};
```
</TabItem>
</Tabs>

</TabItem>
<TabItem value="ts" label="TypeScript">
<Tabs>
<TabItem value="task" label="task.ts" default>

```ts
import { Table, Column, Model, PrimaryKey, ForeignKey, BelongsTo, AutoIncrement, DataType, AllowNull, Default } from "sequelize-typescript";

import User from "./user";

@Table({ tableName: "Tasks" })
class Task extends Model {
@PrimaryKey
@AutoIncrement
@Column(DataType.INTEGER)
id!: number;

@Default(false)
@Column(DataType.BOOLEAN)
complete!: boolean;

@AllowNull(false)
@ForeignKey(() => User)
@Column(DataType.INTEGER)
userID!: number;

@BelongsTo(() => User)
user!: User;
}

export default Task;
```
</TabItem>
<TabItem value="user" label="user.ts">

```ts
import { Table, Column, Model, PrimaryKey, AutoIncrement, DataType, AllowNull, HasMany} from "sequelize-typescript";
import Task from "./task";

@Table({ tableName: "Users" })
class User extends Model {
@PrimaryKey
@AutoIncrement
@Column(DataType.INTEGER)
id!: number;

@AllowNull(false)
@Column
name!: string;

@AllowNull(false)
@Column
email!: string;

@HasMany(() => Task)
task!: Task[];
}

export default User;
```
</TabItem>
</Tabs>
</TabItem>
</Tabs>

We can now generate a migration file by running this command:

```bash
atlas migrate diff --env sequelize
```

Running this command will generate files similar to this in the `migrations` directory:

```
migrations
|-- 20230918143104.sql
`-- atlas.sum
0 directories, 2 files
```

Examining the contents of `20230918143104.sql`:

```sql
-- Create "Users" table
CREATE TABLE `Users` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`createdAt` datetime NOT NULL,
`updatedAt` datetime NOT NULL,
PRIMARY KEY (`id`)
) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
-- Create "Tasks" table
CREATE TABLE `Tasks` (
`id` int NOT NULL AUTO_INCREMENT,
`complete` bool NULL DEFAULT 0,
`createdAt` datetime NOT NULL,
`updatedAt` datetime NOT NULL,
`userID` int NOT NULL,
PRIMARY KEY (`id`),
INDEX `userID` (`userID`),
CONSTRAINT `Tasks_ibfk_1` FOREIGN KEY (`userID`) REFERENCES `Users` (`id`) ON UPDATE CASCADE ON DELETE NO ACTION
) CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
```

Amazing! Atlas automatically generated a migration file that will create the `Users` and `Tasks` tables in our database!

Next, alter the `User` model to add a new `age` field:

<Tabs>
<TabItem value="js" label="JavaScript" default>

```diff
name: {
type: DataTypes.STRING,
allowNull: false
},
+ age: {
+ type: DataTypes.INTEGER,
+ allowNull: false
+ },
```
</TabItem>
<TabItem value="ts" label="TypeScript">

```diff
@AllowNull(false)
@Column
name!: string;

+ @AllowNull(false)
+ @Column
+ age!: number;
```
</TabItem>
</Tabs>

Re-run this command:

```bash
atlas migrate diff --env sequelize
```

Observe a new migration file is generated:

```sql
-- Modify "Users" table
ALTER TABLE `Users` ADD COLUMN `age` int NOT NULL;
```

### Conclusion

In this post, we have presented how Sequelize projects can use Atlas to automatically
plan schema migrations based only on their data model.

#### How can we make Atlas better?

We would love to hear from you [on our Discord server](https://discord.gg/zZ6sWVg6NT) :heart:.
5 changes: 5 additions & 0 deletions doc/website/blog/authors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ datdao:
name: Dat Dao
url: "https://github.com/datdao"
image_url: "https://avatars.githubusercontent.com/u/16095902?v=4"
ronenlu:
name: Ronen Lubin
title: Software Engineer
url: "https://github.com/ronenlu"
image_url: "https://avatars.githubusercontent.com/u/63970571?v=4"

0 comments on commit 21aaa83

Please sign in to comment.