Nextjs配置TypeORM
Contents
1 创建Next.js项目
|
|
按照提示填写项目名称后, 项目就搭建完成了,以下是项目文件结构
|
|
2 docker启动postgresql
- 创建数据库目录/blog-data并添加至
.gitignore
- Docker启动PostgreSQL
|
|
-
释义:
- -v 本地目录:容器目录 或 -v 容器目录——表示将本机某目录,挂载到镜像中某目录下
- -d——后台运行容器
- -p——端口映射,指定主机的5432端口映射到容器的5432端口
- -it——以交互模式运行启动容器
- -e——指定容器环境变量
-
windows系统需要将
$PWD
去掉 -
如果下载慢,先搜阿里云镜像(需要登录阿里云),再搜Docker加速
-
容器搭建结束后会返回容器的id
-
docker ps -a
查看全部容器运行状态 -
docker logs id
查看容器启动日志
- 验证是否成功启动
docker exec -it 容器id bash
使用bash命令行进入容器psql -U blog -W
进入postgres数据库(没有设置密码)- 执行pg命令
\l
: list databases\c
:connect to a database\dt
:display tables
- 创建数据库
由于TypeORM没有提供单纯创建数据库的API,只能使用SQL语句来创建数据库
|
|
分别创建blog_dev
、blog_product
、blog_test
三个数据库
3 Next.js安装TypeORM
安装相关依赖
|
|
修改tsconfig.json
|
|
在初始化TypeORM前,应提交一次代码,用来恢复被初始化后的文件
使用npx执行TypeORM初始化命令
|
|
利用checkout命令将.gitignore
、package.json
和tsconfig.json
恢复至上次最新提交的状态
|
|
最终初始化的结果为新增了src文件夹与ormconfig.json
,修改ormconfig
|
|
初始化完成
4 统一TypeScript编译
TypeORM建议使用ts-node运行ts,而Next.js默认使用内置的babel转义ts为js后再用node来运行。因此我们统一用babel和node来编译和运行ts。
安装@babel/cli
|
|
利用npx执行babel命令,将src文件编译为js文件,存入dist文件夹下
|
|
创建.babelrc,在next内置babel的基础上添加装饰器插件
|
|
修改ormconfig.json中的entities、migrations和subscribers,否则迁移数据库等操作会报错
|
|
修改tsconfig.json,将module改成commonjs
运行node ./dist/index.js后,控制台打印connection的信息,说明已成功连接数据库服务
5 使用migration创建表
利用npx执行typeorm migration创建
|
|
得到src/migrations/{TIMESTAMP}-CreatePost.ts
,编写up和down函数(分别用于升级与降级数据库)
|
|
将编译指令与执行数据库迁移指令封装到package.json的scripts属性
|
|
执行同步数据库命令后,进入docker检查对应数据库是否同步成功
- 优化
package.json
的scripts
,启动next项目的同时自动开启babel编译,并监听代码变动
|
|
6 数据映射到实体
将新建entity的命令添加至scripts
|
|
执行创建命令
|
|
执行后,src文件夹下新增了entity
文件夹,以及其中的Post.ts
,新建代码如下
|
|
7 操作实体
-
TypeORM为我们提供了两套操作实体的API,分别是
Manager
和Repository
,这里选择ManagerAPI -
Manager API的封装思路就是将数据的具体操作交给manager,把User实体类,user1对象和其它参数传给manager即可,Manager常用API如下:
await manager.find(User, { name: 'frank' })
await manager.create(User, { name: '...' })
await manager.save(user1)
await manager.save([user1, user2, user3])
await manager.remove(user1)
(涉及到事务操作)await manager.update(User, 1, { name: 'frank' })
await manager.delete(User, 1)
await manager.findOne(User, 1)
-
Repository API的封装思路为先通过User构造一个repo对象,这个repo对象就只操作User表了,具体示例如下:
-
1 2 3
const userRepository = getRepository(User) await userRepository.findOne(1) await userRepository.save(user)
-
-
测试manager API,回到index.ts:
|
|
- 可能会出现不支持装饰器语法的bug,添加babel插件即可
|
|
- 测试结果:
|
|
8 数据填充
-
目前数据库与数据表都完成了,但是没有数据,可以通过
seed脚本
来构造数据 -
将src下的
index.ts
改为seed.ts
-
方便新增数据,改写entity下的Post.ts,添加构造器函数
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; // posts表的实体 @Entity("posts") export class Post { // 自增 主键 @PrimaryGeneratedColumn("increment") id: string; // 类型为varchar的列 @Column("varchar") title: string; // 类型为text的列 @Column("text") content: string; // Partial的作用在于 不需要将Post的全部属性都传递过来 constructor(attributes: Partial<Post>) { // 将attributes的全部属性放到this上即可 Object.assign(this, attributes); } }
-
Seed.ts
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import "reflect-metadata"; import { createConnection } from "typeorm"; import { Post } from "./entity/Post"; createConnection() .then(async (connection) => { const { manager } = connection; const posts = await manager.find(Post); if (posts.length === 0) { await manager.save( [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((v) => { return new Post({ title: `Post ${v}`, content: `这是我的第${v}篇博客`, }); }) ); console.log(`posts数据填充完成!`); } connection.close(); }) .catch((error) => console.log(error));
-
执行结果
-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$ node ./dist/seed posts数据填充完成! blog_dev=# select * from posts; id | title | content ----+---------+-------------------- 1 | Post 1 | 这是我的第1篇博客 2 | Post 2 | 这是我的第2篇博客 3 | Post 3 | 这是我的第3篇博客 4 | Post 4 | 这是我的第4篇博客 5 | Post 5 | 这是我的第5篇博客 6 | Post 6 | 这是我的第6篇博客 7 | Post 7 | 这是我的第7篇博客 8 | Post 8 | 这是我的第8篇博客 9 | Post 9 | 这是我的第9篇博客 10 | Post 10 | 这是我的第10篇博客 (10 rows)
Author gsemir
LastMod 2022-01-11