TS + TypeORM 踩坑实践 (一) hello ORM

8,016 阅读6分钟

最近完成了一个简易的博客系统,使用 Nextjs + TypeORM + TypeScript。功能有:登录注册、博客增删改、自动部署、Docker 容器化、Nginx gzip 配置。代码地址: github.com/Maricaya/ne… 。预览地址:http://121.36.50.175/。不得不说 SSR 真香,几乎没有白屏时间,加载非常快。 上次说道Next.js + TS 入门之项目搭建, 这次来看看如何为我们的项目引入 TypeORM。

对象关系映射(英语:Object Relational Mapping,简称ORM), 是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。 从效果上说,它其实是创建了一个可以在编程语言里使用的“虚拟对象数据库”。

— Wikipedia —对象关系映射 ORM

什么是 ORM

不了解后端的同学可能不知道这个概念,我来简单说明一下。

在操作数据库的时候,我们都知道会使用大量 SQL 语句,类似这样:

SELECT * FROM users where WHERE email ='test@test.com';

而用 ORM 库的时候,我们的代码看起来像这样:

var orm = require('one-orm-libarry');
var user = orm("users").where({ email: 'test@test.com' });

以上两段代码执行了完全相同的查询,但是我们可以使用更熟悉的 js 操作数据库。

ORM 也会帮我们封装一些复杂操作,让我们更容易对数据库进行操作。

所以,对于小白来说,在项目中加入 ORM 是很有必要的。(大神可以自己编写高性能 SQL)

TypeORM 简介

TypeORM 是专门将 JavaScript / TypeScript 之间的数据,转换为各种数据库的 ORM。 它支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js 之间的迁移。

目前的它版本是 0.2.25,虽然没有正式发布 v1.0.0,但是它在 Node.js 社区非常受欢迎。

它支持关联 Associations、事务 Transaction、数据库迁移 Migration 这些基本操作。

最重要的是,它默认支持 TypeScript。

了解了 TypeORM,我们来动手写代码。首先,要创建一个数据库。

创建数据库

在 docker 中创建 PostgreSQL

最终我选择在 Docker 容器创建 PostgreSQL 数据库 blog,并将容器的端口5432暴露给主机。 (我默认你已经会了简单的 docker 操作)

mkdir blog-data
docker run -v "$PWD/blog-data":/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2

SQL 创建

一个成熟的 ORM 框架应该给我们提供创建数据库的 API,但是 TypeORM没有 =.=

正式的开发流程下需要创建三个数据库:开发 development、测试 test、生产 production。

我们这次创建开发数据库 blog_development 就可以啦。

先进入 docker 容器,docker exec --it 容器id bash。 再进入 pg,psql -U blog -W

CREATE DATABASE blog_development ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';

\l 查看当前数据库

ok,没问题!接下来为我们的项目安装 TypeORM。

安装 TypeORM

接着上次的代码,首先安装依赖

yarn add typeorm reflect-metadata @types/node pg

然后修改 tsconfig

"compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
}

我们运行 npx typeorm init --database postgres,初始化 Typeorm。

注意这个命令会修改 .gitignore/package.json/tsconfig.json 文件,记得先还原一下, 后面我们具体看有什么需要修改的。

这个命令还帮我们创建了一些文件:

src
│   ├── entity        // 实体
│   │   └── User.ts
│   ├── index.ts
│   └── migration     // 迁移

我们不看官方给的例子,删掉 User.ts,将 index.ts 中有关 User.ts 都删去。(后面会自己创建 entity 文件)

最后在 ormconfig.json 配置我们的数据库信息:

"username": "blog",
"password": "",
"database": "blog_development",

重要配置:禁用 sync

重点:在 ormconfig.json 中添加配置:"synchronize": false

sync 的意思是同步,如果为 true,每次在连接数据库时, typeorm 会检查当前的 entity 在数据库中是否有对应的表, 如果没有,它会自动根据 entity 目录来修改数据表。

假设 entity 里面有 User,就会自动创建 User 表,也就是自动同步代码和数据库。

为什么禁用

没有经验的同学可能有这样的疑问,看起来很方便啊,为什么要禁用?

因为 sync 功能可能会在外面修改 User 的时候直接删数据。

比如我们把 User 的 name 改为 nickName,TypeORM 认为你删除了 name,新增了 nickName。 这样,它会把所有 name 都删掉,创建一个空的列 nickName。

这种行为绝对不能发生在生产环境中,所以我们从一开始就禁止 sync 功能。

如何运行 TypeScript

在 TypeScript 社区,有两种方法运行 TypeScript。 一种是使用 babel 来将 TS 编译为 JS,另一种是使用 ts-node 直接运行。

这两种运行方式对 TS 的支持并不是完全一致的,如果一起用会有很多不兼容的问题。

TypeORM 推荐使用 ts-node 来编译,但是我用的另一个框架 Next.js 内置使用 babel 来运行 TS。

所以我们必须进行统一,全部都用 babel。

步骤

1. 安装 babel

先将 Node.js 升级到 v14,然后安装 @babel/cli yarn add @babel/cli

然后使用 babel 将 src 中的文件编译为 JavaScript。

npx babel ./src --out-dir dist --extensions ".ts,.tsx"

但是没有运行成功,报了一堆错。

2. 安装 plugin-proposal-decorators

我们来看看错误提示,根据提示搜索答案:

Support for the experimental syntax 'decorators-legacy' isn't currently enabled

这个帖子和我们的报错一样,最高赞答案说

I had the same problem, but I was able to get it working by running npm install --save-dev @babel/plugin-proposal-decorators and adding ["@babel/plugin-proposal-decorators", { "legacy": true }] to the plugins section in my .babelrc.

我们来跟着操作一下,先安装插件:

yarn add --dev @babel/plugin-proposal-decorators

去 Next.js 官网查看 .babelrc 默认配置,发现是支持自定义 .babelrc 的。

我们创建 .babelrc,先添加 Next.js 的默认配置。

{
  "presets": ["next/babel"],
  "plugins": []
}

然后添加插件 ["@babel/plugin-proposal-decorators", { "legacy": true }]

重新运行刚刚失败的命令,成功啦。

翻译后的文件:

dist
│   ├── entity
│   │   └── User.js
│   └── index.js

得到翻译之后的文件 dist 里面的 JS,但是此时直接运行 node dist/index.js 还是会报错。

3. 修改 ormconfig.json

因为 ormconfig.json 还是默认配置, 我们运行 node dist/index.js 还是会去找 src 下的 entity、migrations、subscribers 文件。

修改 ormconfig:

"entities": [
  "dist/entity/**/*.js"
],
"migrations": [
  "dist/migration/**/*.js"
],
"subscribers": [
  "dist/subscriber/**/*.js"
]

重新运行 node dist/index.js,成功啦!

工作流总结

来看看到目前为止我们做了什么:

  • 统一让 Next.js 和 TypeORM 使用 babel 翻译 TS
  • TypeORM
    • 每次修改 src 的 TS 代码后,都需要翻译为 JS 文件,存入 dist 目录中
    • 使用 node 运行 dist 里的 JS,执行 TypeORM 任务
  • Next.js
    • pages 目录下的 TS 文件修改后,浏览请求的时候,Next.js 会将 TS 翻译为 JS 文件,放在 .next 文件夹中
      • 使用 node 运行 dist 里的 JS

介绍完了如何在项目中引入 TypeORM,以及一些基本配置,下一篇我们来看看如何使用 TypeORM 操作数据库。