-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
doc/website/blog: announcing external loader for sequelize post
- Loading branch information
Showing
2 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
381 changes: 381 additions & 0 deletions
381
doc/website/blog/2023-09-25-sequelize-schema-loader.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters