新建项目文件夹
$ mkdir demo
$ cd demo
初始化TypeScript配置
$ npx tsc --init
安装 Sequelize Sequelize-cli
$ npm install --save-dev @types/node @types/validator
$ npm install sequelize reflect-metadata sequelize-typescript
$ npm install --save-dev ts-node @types/express @types/node @types/sequelize
调整部分TypeScript配置
$ vi tsconfig.json
"target": "es2022", "experimentalDecorators": true, "emitDecoratorMetadata": true"useDefineForClassFields": false,"module": "es2022", "moduleResolution": "node", "baseUrl": "./src","allowJs": true, "allowSyntheticDefaultImports": true,"skipLibCheck": true,
使用一个sequelize配置文件,并进行sequelize初始化
$ vi .sequelizerc
// .sequelizerc
const path = require('path');module.exports = {config: path.resolve('src/database', 'sequelize.config.js'),'models-path': path.resolve('src/database', 'models'),'seeders-path': path.resolve('src/database', 'seeders'),'migrations-path': path.resolve('src/database', 'migrations'),
};
$ npx sequelize init
修改sequelize生成的数据库配置信息,使用sqlite,以及项目目录结构
$ vi src/database/sequelize.config.js
--> .cjs
require('ts-node/register');module.exports = {env: 'development',dialect: 'sqlite',storage: './database.sqlite'
};
生成User信息model
$ npx sequelize-cli model:generate --name User --attributes firstname:string,lastname:string,email:string,password:string,memo:string
会创建两个文件
- models/user.js
- migrations/XXXXXXXXXXXXXX-create-user.js
其中:XXXXXXXXXXXXXX- 是按时间戳生成的随机文件名
再生成一个美食Model
$ npx sequelize-cli model:generate --name Recipe --attributes userid:integer,title:string,ingredients:string,direction:string,memo:string
会创建两个文件
- models/recipe.js
- migrations/XXXXXXXXXXXXXX-create-recipe.js
其中:XXXXXXXXXXXXXX- 是按时间戳生成的随机文件名
用Model产生的数据定义脚本在数据库建表
$ npx sequelize-cli db:migrate
执行后在demo目录下生成一个database.sqlite文件,存放User信息model、美食Model表结构,除指定字段信息外,还有sequelize附加的字段。
测试创建一个user
$ npx sequelize-cli seed:generate --name first-user
会创建在seeders目录
- migratiseedersons/XXXXXXXXXXXXXX-first-user.js
执行测试文件在数据库User表增加用户信息
$ npx sequelize-cli db:seed:all
修改美食Model增加关联到User信息Model
// associate
Recipe.belongsTo(User, {as: 'user',foreignKey: {name: 'userid',allowNull: false,},foreignKeyConstraint: true,
});
修改User信息Model关联到美食Model
// associate
User.hasMany(Recipe,{as: 'recipes',foreignKey: {name: 'userid',allowNull: false,},foreignKeyConstraint: false,
})
创建sequelize数据连接文件
$ vi src/database/connect.ts
import {Sequelize} from 'sequelize-typescript';
import {Dialect} from 'sequelize';
import { DIALECT,DB_STORAGE,DB_DATABASE, DB_TIMEZONE, DB_HOST, DB_PORT, DB_PASSWORD, DB_USERNAME } from '../configs.js';const sqlite: Dialect = DIALECT as Dialect;
let opt = {dialect: sqlite,storage: DB_STORAGE,database: DB_DATABASE,username: DB_USERNAME,password: DB_PASSWORD,host: DB_HOST,port: DB_PORT,//timezone: DB_TIMEZONE,
}
const dataSource: Sequelize = new Sequelize(opt);export default dataSource;
安装 GraphQL Server 、 GraphQL以及其他组件包
$ npm install @apollo/server http express graphql graphql-tag bcryptjs
创建GraphQL的typeDefs文件
$ vi src/graphql/api/typeDefs.ts
import gql from 'graphql-tag';export const typeDefs = gql`#graphqltype User {id: Int!firstname: String!lastname: String!email: String!memo: Stringrecipes: [Recipe!]!}type Recipe {id: Int!title: String!ingredients: String!direction: String!memo: Stringuser: User!}type Query {user(id: Int!): UserallRecipes: [Recipe!]!recipe(id: Int!): RecipeallUsers: [User!]!}type Mutation {createUser(firstName: String!, lastName: String!, email: String!, password: String!, memo: String): UsercreateRecipe(userid: Int!title: String!ingredients: String!direction: String!memo: String): Recipe}
`;
export default typeDefs;
创建GraphQL的resolvers文件
$ vi src/graphql/api/resolvers.ts
import bcrypt from 'bcryptjs';import User from '../../database/models/user.model.js';
import Recipe from '../../database/models/recipe.model.js';export const resolvers = {Query: {user: async (root: any, args: { id: any; }, context: any, info: any) => {return User.findByPk(args.id);},allRecipes: async (parent: any, args: {}, context: any, info: any) => {return Recipe.findAll(info);},recipe: async (parent: any, args: { id: any; }, context: any, info: any) => {return Recipe.findByPk(args.id);},allUsers: async (parent: any, args: {}, context: any, info: any) => {return User.findAll();},},Mutation: {async createUser(parent: any,args: { firstname: any, lastname: any, email: any, password: any, memo: any },context: any,info: any) {return User.create({firstname: args.firstname,lastname: args.lastname,email: args.email,password: await bcrypt.hash(args.password, 10),memo: args.memo});},async createRecipe(parent: any,args: { userid: any, title: any, ingredients: any, direction: any, memo: any },context: any,info: any) {return Recipe.create({userid: args.userid,title: args.title,ingredients: args.ingredients,direction: args.direction,memo: args.memo});},},User: {async recipes(user: any) {return user.getRecipes();},},Recipe: {async user(recipe: any) {return recipe.getUser();},},};export default resolvers;
创建项目主文件,使用Apollo组件包
$ vi src/index.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';import cors from 'cors';
import express from 'express';
import http from 'http';
import pkg from 'body-parser';
const { json } = pkg;import { typeDefs } from './graphql/api/typeDefs.js';
import { resolvers } from './graphql/api/resolvers.js';
import dbTables from './database/models/index.js';
import { SERVICE_PORT } from "./configs.js";const app = express();
// Our httpServer handles incoming requests to our Express app.
// Below, we tell Apollo Server to "drain" this httpServer,
// enabling our servers to shut down gracefully.
const httpServer = http.createServer(app);interface ContextValue {dbTables?: typeof dbTables;
}const server = new ApolloServer({typeDefs,resolvers,plugins: [// Proper shutdown for the HTTP server.ApolloServerPluginDrainHttpServer({ httpServer }),],
});
await server.start();app.use(cors(),json(),expressMiddleware(server, {context: async ({ req }) => ({token: req.headers.token,dbTables: dbTables,}),}),
);const port = SERVICE_PORT || 4000;app.listen({ port: port }, () => console.log(`Server ready at http://localhost:${port}`))
修改 package.json, 增加编译和执行
$ vi package.json
"scripts": {"build": "tsc","start": "nodemon --exec ts-node-esm build/index.js","dev": "node --loader ts-node/esm --no-warnings=ExperimentalWarning src/index.ts",},
最终来到运行和燕子示例
$ npm run build
$ npm run start
或者
$ npm run dev