Sequelize + TypeScript - 你需要知道的
嗨,开发者社区👋🏾!
我刚刚决定要写一篇文章,所以就来了。我之前不知道该写什么,最后想到了Sequelize + TypeScript ,分享一下我使用 Sequelize 并添加 TypeScript 的经验。
目录
一点背景知识
这个想法来自于我正在管理的一个项目,客户决定他们希望昨天(像往常一样)交付项目,所以公司组建了一个由初级工程师组成的小组,加上我作为高级/首席工程师,这样我就可以负责选择堆栈,所以我决定使用:
- Express/NodeJS(后端)
- PostgreSQL(数据库)
- 普通 React(前端)
- 续篇(ORM)
由于初级工程师都没有使用 TypeScript 进行开发的经验,也没有使用基于微服务的架构的经验,所以我决定使用简单的 JS 和单片架构对我们来说会很好,因为这是他们目前所了解的,而且我不想减慢项目进度。
现在我听到你在问这和 Sequelize + TypeScript 有什么关系?嗯,完全相关。公司决定将这个项目作为另一个项目的基础,这时我们才意识到当前设置存在的所有限制和问题。仅举几例:
- 后端和前端都有很多重复的代码
- 缓慢的开发环境
- 导致项目崩溃的奇怪错误
- 很难理解什么在做什么
- 数据库模型混乱
- 还有更多...
因此,我在这里使用 TypeScript 重构项目,并设置基于事件通信的基于微服务的架构(应该如此),同时我的团队正在修复错误并等待我完成重构。
好了,说得够多了,让我们深入研究并设置 Sequelize 来使用 TypeScript。
设置 Sequelize
我将使用sequelize-cli工具,该工具根据.sequelizerc
文件生成 Sequelize 文件夹结构和配置,如下所示
const path = require('path');
module.exports = {
config: path.resolve('.', 'config.js'),
'models-path': path.resolve('./db/models'),
'seeders-path': path.resolve('./db/seeders'),
'migrations-path': path.resolve('./db/migrations')
};
现在您可以运行npx sequelize-cli init
,您将拥有以下项目结构
- config.js
- db/
- migrations/
- models/
- index.js
- seeders/
您的db/models/index.js
文件将如下所示
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require('../../app/config')[env];
const db = {};
let sequelize;
if (config.url) {
sequelize = new Sequelize(config.url, config);
} else {
// another sequelize configuration
}
fs.readdirSync(__dirname)
.filter(file => {
return (
file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
);
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
是的,一堆代码,但这实际上简化了模型的使用,并减少了requires/imports
您将来在模型中执行的操作量。
很酷的东西,不过我听说你想用 TypeScriptindex.js
。好的,那我们把第一个文件改成 TypeScript 吧。把文件重命名为index.ts
,然后修改一下里面的内容
import { Sequelize } from 'sequelize';
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../../config.js')[env];
const sequelize = config.url
? new Sequelize(config.url, config)
: new Sequelize(config.database, config.username, config.password, config);
export { Sequelize, sequelize };
是的,就是这样!
好的,让我解释一下为什么我们从文件中删除了一堆代码,原因是TypeScript 本身就是 TypeScript。我只是不想db
根据目录中的文件生成对象,而需要对其进行类型化models/
。所以我们删除了所有这些代码,只导出了Sequelize
(Class) 和sequelize
(instance)。sequelize
实例引用了数据库连接,因此在创建模型时,我们可以使用这个实例,这样就可以通过模型与数据库进行通信。
第一个文件已完成,还有一堆文件需要处理。
设置 Sequelize 模型
如果我们使用 JS,Sequelize 模型将会如下所示。
注意:我将声明两个示例模型:Author 和 Book。
首先我们来看看如何使用 JS 添加 Sequelize 模型。作者模型如下所示
// /models/author.js
module.exports = (sequelize, DataTypes) => {
const Author = sequelize.define(
'Author',
{
id: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: DataTypes.UUID,
unique: true,
},
firstName: {
allowNull: true,
type: DataTypes.TEXT,
},
lastName: {
allowNull: false,
type: DataTypes.TEXT,
},
email: {
allowNull: true,
type: DataTypes.TEXT,
},
},
{}
);
return Author;
};
所以让我们也用 TypeScript 来处理 Author 模型。将名称改为author.ts
,然后定义一些接口
import { Model, Optional } from 'sequelize';
interface AuthorAttributes {
id: string;
firstName: string;
lastName: string;
email: string;
};
/*
We have to declare the AuthorCreationAttributes to
tell Sequelize and TypeScript that the property id,
in this case, is optional to be passed at creation time
*/
interface AuthorCreationAttributes
extends Optional<AuthorAttributes, 'id'> {}
interface AuthorInstance
extends Model<AuthorAttributes, AuthorCreationAttributes>,
AuthorAttributes {
createdAt?: Date;
updatedAt?: Date;
}
我们需要这些接口来告诉 TypeScript 作者实例具有哪些属性。
现在让我们以 TS 方式定义 Author 模型
// model/author.ts
import { sequelize } from '.';
// ... instances code
const Author = sequelize.define<AuthorInstance>(
'Author',
{
id: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: DataTypes.UUID,
unique: true,
},
firstName: {
allowNull: true,
type: DataTypes.TEXT,
},
lastName: {
allowNull: false,
type: DataTypes.TEXT,
},
email: {
allowNull: true,
type: DataTypes.TEXT,
},
}
);
现在你应该会想,如果我最终还是会这么做,为什么还要经历所有这些?好吧,如果你在模型定义对象的任何属性上输入了拼写错误,TypeScript 会告诉你X
缺少该属性。同样,如果你尝试添加一个接口上未定义的属性,AuthorAttributes
TypeScript 也会告诉你该属性Y
未在类型上定义。AuthorAttributes
现在我们知道如何使用 TypeScript 声明 Sequelize 模型,让我们定义该book.ts
模型。
// /models/book.ts
import { Model, Optional } from 'sequelize';
import { sequelize } from '.';
interface BookAttributes {
id: string;
title: string;
numberOfPages: number;
authorId: string;
}
interface BookCreationAttributes
extends Optional<BookAttributes, 'id'> {}
interface BookInstance
extends Model<BookAttributes, BookCreationAttributes>,
BookAttributes {
createdAt?: Date;
updatedAt?: Date;
}
const Book = sequelize.define<BookInstance>(
'Book',
{
id: {
allowNull: false,
autoIncrement: false,
primaryKey: true,
type: DataTypes.UUID,
unique: true,
},
title: {
allowNull: true,
type: DataTypes.TEXT,
},
numberOfPages: {
allowNull: false,
type: DataTypes.INTEGER,
},
authorId: {
allowNull: true,
type: DataTypes.UUID,
},
}
);
export default Book;
就是这样!这就是使用 TypeScript 定义 Sequelize 模型的方法。
添加模型关联
如果你想在模型中添加关联,那么An Author HAS MANY Books
在 JS 中你可以这样做
// model/author.js
Author.associate = models => {
Author.hasMany(models.Book, {
foreignKey: 'authorId',
});
};
// model/book.js
Book.associate = models => {
Book.belongsTo(models.Author, {
foreignKey: 'id'
});
};
那么,你还记得我说过,运行时生成的代码npx sequelize-cli init
会在我们尝试做事时提供帮助吗requires/imports
?现在是时候了!
如果定义了关联函数,Sequelize 将尝试运行该函数,并将定义的关联添加到模型中。
但是我们这里讨论的是 TypeScript,而不是 JS,那么如何在 TypeScript 中定义的模型中做到这一点呢?让我们看看怎么做。
// models/author.ts
import Book from './book';
// ... Code Defining the Author Model
Author.hasMany(Book, {
/*
You can omit the sourceKey property
since by default sequelize will use the primary key defined
in the model - But I like to be explicit
*/
sourceKey: 'id',
foreignKey: 'authorId',
as: 'books'
});
// models/book.ts
import Author from './author';
// ... Code Defining the Book Model
Book.belongsTo(Author, {
foreignKey: 'authorId',
as: 'author'
});
Is that it?
我听到你这么说了。好吧,如果这世上的事真这么简单,我就不会写这篇文章了😅。
那么为什么这行不通呢?Sequelize 的工作方式很奇怪,如果我们尝试在各自的文件中定义这种关联(就像在 JS 中一样),Sequelize 会抛出这个错误:
throw new Error("${source.name}.${_.lowerFirst(Type.name)} called with something that's not a subclass of Sequelize.Model");
^
"Error: Book.belongsTo called with something that's not a subclass of Sequelize.Model"
起初,我以为我漏掉了什么,或者我没有将 Author 模型的类型设置为 Sequelize 模型,但仔细检查一切正常后,TypeScript 并没有报错,因为我尝试belongsTo
使用非 Sequelize 模型来调用该方法。所以我很想知道为什么会发生这种情况?🤔
我阅读了 Sequelize 文档和网上的帖子,但没找到任何可行的方法,只有一个人(抱歉,没找到 StackOverflow 帖子的链接)建议将关联关系放在一个模型文件中。我对此持怀疑态度,因为我心想:既然我导出的模型和我的文件中的模型是同一个,这怎么可能行得通呢?不过,我还是尝试了一下。我将调用移到了文件belongsTo
中models/author.ts
:
// model/author.ts
import Book from './book';
// ... a bunch of code here
Book.belongsTo(Author, {
foreignKey: 'authorId',
as: 'author'
});
上帝保佑,我的项目正在运行:
The library Server is running on http://localhost:3000
这很奇怪,但似乎您必须在设置关联belongsTo
的同一文件中定义关联。hasMany
hasOne
结论
最后,使用 TypeScript 设置 Sequelize 一点也不难,现在我们知道哪些属性属于哪个模型。
对我来说,这是一次宝贵的经验,因为我认为将我的 JS 文件转换为 TS 只需通过重命名文件即可,因为我相信 Sequelize 会在后台处理输入工作。
注意:我知道有一个名为sequelize-typescript的包,但我不想在我的项目中添加任何依赖项,而且这个包也不是由 Sequelize 团队维护的。不过,我刚刚尝试了一下,感觉还不错,所以如果你不想像我一样,可以试试。
到最后,我不想在我的项目中再添加一个依赖项,而且我能够仅使用纯 TS 来实现将 TypeScript 添加到 Sequlize 的目标,而无需第三方库。
抱歉,这篇文章太长了,但如果您想同时使用 Sequelize 和 TypeScript,我希望它对您有所帮助。👋🏾
鏂囩珷鏉ユ簮锛�https://dev.to/jctaveras/sequelize-typescript-what-you-need-to-know-41mj