使用Node.js、Sequelize和MS SQL Server构建的教程
其他版本可用。
在本教程中,我们将介绍如何构建一个Node.js API,将数据存储在Microsoft SQL Server中,并支持以下功能:用户注册、用JWT认证登录、用户管理/CRUD操作。该示例API使用Sequelize和Tedious连接到SQL Server。
Sequelize
Sequelize是一个Node.js ORM(对象关系映射器),用于连接、查询和管理关系数据库的数据。它与多个数据库兼容,包括MS SQL Server。Sequelize还支持模型同步,根据代码中定义的模型(如用户模型)自动生成数据库表和列。
Tedious
Tedious是一个专门针对MSSQL的数据库客户端库,用于与SQL Server的实例进行交互。它是Sequelize为MSSQL使用的底层连接器库。它也直接用于MSSQL数据库包装器中,如果db不存在,它就会自动创建一个SQL Server数据库。
用React、Angular或Vue连接API
Node.js API可以很容易地连接到一个用React、Angular或Vue构建的前端应用程序的例子。请看下面的说明。
GitHub上的代码
该项目可在GitHub上找到,网址是github.com/cornflourbl…
Node + SQL Server教程内容
本教程所需的工具
要遵循本教程中的步骤,你需要以下东西。
在本地运行Node.js + MSSQL API
- 从github.com/cornflourbl…下载或克隆项目源代码
- 通过在项目根目录(package.json所在的位置)的命令行运行
npm install或npm i,安装所有需要的npm包。 - 更新
/config.json中的数据库凭证,以连接到你的MS SQL Server实例,以及 确保MSSQL服务器正在运行. - 通过在项目根目录下的命令行运行
npm start(或npm run dev,用nodemon启动)来启动API,你应该看到Server listening on port 4000。 - 按照下面的说明,用Postman进行测试,或者将Node API与其中一个可用的单页应用程序(React、Angular或Vue)连接。
在生产中运行之前
在生产中运行之前,还要确保更新config.json文件中的secret 属性,它是用来签署和验证JWT令牌的认证。把它改成一个随机字符串,以确保没有人可以用同样的秘密生成JWT,并获得对你的api的未经授权的访问。一个快速而简单的方法是把几个GUID连在一起,组成一个长的随机字符串(例如从www.guidgenerator.com/)。
序列化模型同步
在启动时,你也应该看到像下面这样的输出,来自在MSSQL数据库包装器中执行的Sequelize模型同步,它显示了基于Sequelize用户模型的Users 表的创建和同步。
Executing (default): SELECT TABLE_NAME, TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = N'Users' AND TABLE_SCHEMA = N'dbo'
Executing (default): SELECT c.COLUMN_NAME AS 'Name', c.DATA_TYPE AS 'Type', c.CHARACTER_MAXIMUM_LENGTH AS 'Length', c.IS_NULLABLE as 'IsNull', COLUMN_DEFAULT AS 'Default', pk.CONSTRAINT_TYPE AS 'Constraint', COLUMNPROPERTY(OBJECT_ID(c.TABLE_SCHEMA+'.'+c.TABLE_NAME), c.COLUMN_NAME, 'IsIdentity') as 'IsIdentity', CAST(prop.value AS NVARCHAR) AS 'Comment' FROM INFORMATION_SCHEMA.TABLES t INNER JOIN INFORMATION_SCHEMA.COLUMNS c ON t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_SCHEMA = c.TABLE_SCHEMA LEFT JOIN (SELECT tc.table_schema, tc.table_name, cu.column_name, tc.CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE cu ON tc.table_schema=cu.table_schema and tc.table_name=cu.table_name and tc.constraint_name=cu.constraint_name and tc.CONSTRAINT_TYPE='PRIMARY KEY') pk ON pk.table_schema=c.table_schema AND pk.table_name=c.table_name AND pk.column_name=c.column_name INNER JOIN sys.columns AS sc ON sc.object_id = object_id(t.table_schema + '.' + t.table_name) AND sc.name = c.column_name LEFT JOIN sys.extended_properties prop ON prop.major_id = sc.object_id AND prop.minor_id = sc.column_id AND prop.name = 'MS_Description' WHERE t.TABLE_NAME = 'Users'
Executing (default): SELECT constraint_name = OBJ.NAME, constraintName = OBJ.NAME, constraintCatalog = 'node-mssql-registration-login-api', constraintSchema = SCHEMA_NAME(OBJ.SCHEMA_ID), tableName = TB.NAME, tableSchema = SCHEMA_NAME(TB.SCHEMA_ID), tableCatalog = 'node-mssql-registration-login-api', columnName = COL.NAME, referencedTableSchema = SCHEMA_NAME(RTB.SCHEMA_ID), referencedCatalog = 'node-mssql-registration-login-api', referencedTableName = RTB.NAME, referencedColumnName = RCOL.NAME FROM sys.foreign_key_columns FKC INNER JOIN sys.objects OBJ ON OBJ.OBJECT_ID = FKC.CONSTRAINT_OBJECT_ID INNER JOIN sys.tables TB ON TB.OBJECT_ID = FKC.PARENT_OBJECT_ID INNER JOIN sys.columns COL ON COL.COLUMN_ID = PARENT_COLUMN_ID AND COL.OBJECT_ID = TB.OBJECT_ID INNER JOIN sys.tables RTB ON RTB.OBJECT_ID = FKC.REFERENCED_OBJECT_ID INNER JOIN sys.columns RCOL ON RCOL.COLUMN_ID = REFERENCED_COLUMN_ID AND RCOL.OBJECT_ID = RTB.OBJECT_ID WHERE TB.NAME ='Users'
Executing (default): ALTER TABLE [Users] ALTER COLUMN [firstName] NVARCHAR(255) NOT NULL;
Executing (default): ALTER TABLE [Users] ALTER COLUMN [lastName] NVARCHAR(255) NOT NULL;
Executing (default): ALTER TABLE [Users] ALTER COLUMN [username] NVARCHAR(255) NOT NULL;
Executing (default): ALTER TABLE [Users] ALTER COLUMN [hash] NVARCHAR(255) NOT NULL;
Executing (default): ALTER TABLE [Users] ALTER COLUMN [createdAt] DATETIMEOFFSET NOT NULL;
Executing (default): ALTER TABLE [Users] ALTER COLUMN [updatedAt] DATETIMEOFFSET NOT NULL;
Executing (default): EXEC sys.sp_helpindex @objname = N'[Users]';
用Postman测试Node + SQL Server Auth API
Postman是一个测试API的好工具,你可以在www.postman.com/downloads。
下面是关于如何使用Postman在api上注册一个新用户,认证一个用户以获得一个JWT令牌,然后用JWT令牌进行认证请求,从api上获取一个用户列表的说明。
如何用Postman注册一个新用户
要在api上注册一个新的用户,请遵循以下步骤。
-
通过点击标签末尾的加号**(+)**按钮打开一个新的请求标签。
-
用URL输入栏左边的下拉选择器将HTTP方法改为POST。
-
在URL字段中输入你的本地API的用户路线的地址。
http://localhost:4000/users/register -
选择URL字段下面的Body标签,将body类型单选按钮改为raw,并将格式下拉选择器改为JSON。
-
在Body文本区中输入一个包含所需用户属性的JSON对象,例如
{ "firstName": "Jason", "lastName": "Watmore", "username": "jason", "password": "my-super-secret-password" } -
点击发送按钮,你应该收到一个 "200 OK "的响应,响应体中有一个成功信息。
这是请求发送后Postman的截图,新用户已经被注册。

如何用Postman验证一个用户
要用api认证一个用户并获得一个JWT令牌,请按照以下步骤进行。
-
通过点击标签末尾的加号**(+)**按钮打开一个新的请求标签。
-
用URL输入栏左边的下拉选择器将HTTP方法改为POST。
-
在URL字段中输入你的本地API的用户路线的地址。
http://localhost:4000/users/authenticate -
选择URL字段下面的Body标签,将body类型单选按钮改为raw,并将格式下拉选择器改为JSON。
-
在Bodytextarea中输入一个包含用户名和密码的JSON对象,如:。
{ "username": "jason", "password": "my-super-secret-password" } -
点击发送按钮,你应该收到一个 "200 OK "的响应,其中包括响应体中的JWT令牌,复制一个令牌值,因为我们将在下一步中使用它来进行认证请求。
下面是Postman在发送请求和用户被认证后的截图。

如何进行认证请求以检索所有用户
要使用上一步的JWT令牌进行认证请求,请遵循以下步骤。
- 点击标签末尾的加号**(+)**按钮,打开一个新的请求标签。
- 用URL输入栏左边的下拉选择器将HTTP方法改为GET。
- 在URL字段中输入你的本地API的用户路线的地址。
http://localhost:4000/users - 选择URL字段下面的授权标签,在类型下拉选择器中把类型改为承载令牌,并把前面验证步骤中的JWT令牌粘贴到令牌字段中。
- 点击发送按钮,你应该收到一个 "200 OK "的响应,其中包含系统中所有用户记录的JSON数组。
下面是Postman在进行认证请求以获得所有用户后的截图。

如何用Postman更新一个用户
要用api更新一个用户,请遵循以下步骤。
-
通过点击标签末尾的加号**(+)**按钮打开一个新的请求标签。
-
用URL输入栏左边的下拉选择器将HTTP方法改为PUT。
-
在URL字段中输入你的本地API的用户路线的地址。
http://localhost:4000/users/1 -
选择URL字段下面的授权标签,在类型下拉选择器中把类型改为承载令牌,并把前面验证步骤中的JWT令牌粘贴到令牌字段中。
-
选择URL字段下面的Body标签,将body类型单选按钮改为raw,并将格式下拉选择器改为JSON。
-
在Bodytextarea中输入一个包含你要更新的属性的JSON对象,例如,更新名字和姓氏。
{ "firstName": "Foo", "lastName": "Bar" } -
点击发送按钮,你应该收到一个 "200 OK "的响应,响应体中有更新的用户信息。
下面是请求发送后Postman的截图,用户已经被更新。

用Node.js API连接React应用程序
关于React应用实例的全部细节,请看文章React Hooks + Redux - 用户注册和登录教程及实例。但要快速启动和运行,只需按照以下步骤。
-
从github.com/cornflourbl…下载或克隆React教程的代码
-
在项目根目录下(package.json所在的位置)通过命令行运行
npm install,安装所有需要的npm包。 -
删除或注释掉位于
/src/index.jsx文件中的注释// setup fake backend下面的2行。 -
通过在项目根目录下的命令行运行
npm start,启动应用程序,这将启动一个显示应用程序的浏览器,它应该与你已经运行的Node.js + MSSQL API相连接。
用Node.js API连接Angular应用程序
关于Angular应用程序例子的全部细节,请参见Angular 10 - 用户注册和登录例子及教程。但要快速启动并运行,只需按照以下步骤即可。
-
从github.com/cornflourbl…下载或克隆Angular 10教程的代码
-
在项目根目录下(package.json所在的位置)通过命令行运行
npm install,安装所有需要的npm包。 -
删除或注释掉位于
/src/app/app.module.ts文件中的注释// provider used to create fake backend下面的一行。 -
通过在项目根目录下的命令行运行
npm start,启动应用程序,这将启动一个显示应用程序的浏览器,它应该与你已经运行的Node.js + MSSQL API相连接。
用Node.js API连接Vue应用程序
关于Vue应用实例的全部细节,请参见文章Vue + Vuex - 用户注册和登录教程及实例。但要快速启动和运行,只需遵循以下步骤。
-
从github.com/cornflourbl…下载或克隆Vue教程的代码
-
在项目根目录下(package.json所在的位置)通过命令行运行
npm install,安装所有需要的npm包。 -
删除或注释掉位于
/src/index.js文件中的注释// setup fake backend下面的2行。 -
通过在项目根目录下的命令行运行
npm start,启动应用程序,这将启动一个显示应用程序的浏览器,它应该与你已经运行的Node.js + SQL Server API相连接。
Node + MS SQL Server API项目结构
该教程项目的结构分为特征文件夹(用户)和非特征/共享组件文件夹(_helpers,_middleware)。共享组件文件夹包含可在多个功能或应用程序的其他部分使用的代码,其前缀为下划线_ ,以便将其分组,并使其易于区分特定功能和共享代码。
auth例子目前只包含一个单一的(用户)功能,但通过复制用户文件夹并遵循同样的模式,可以很容易地扩展出其他功能。
点击下面的任何一个链接,可以跳转到每个文件的描述,以及它的代码。
帮助者文件夹
帮助者文件夹包含了所有不适合其他文件夹但又没有理由拥有自己的文件夹的零碎文件。
MSSQL服务器数据库封装器
MSSQL数据库包装器使用Sequelize和tedious 客户端连接到MSSQL,并输出一个对象,为应用程序公开所有的数据库模型(目前只有User )。它提供了一个简单的方法来从一个点访问数据库的任何部分。
initialize() 函数在API启动时被执行一次,并执行以下操作。
- 使用
tediousdb客户端连接到MS SQL Server,如果API数据库不存在,则执行查询以创建该数据库。 - 用Sequelize ORM连接到API数据库。
- 初始化
User模型,并将其附加到导出的db对象。 - 通过调用
await sequelize.sync({ alter: true }),自动创建/更新SQL Server数据库中的表以匹配Sequelize模型(如果需要)。关于Sequelize模型同步选项的更多信息,见sequelize.org/master/manu…
const tedious = require('tedious');
const { Sequelize } = require('sequelize');
const { dbName, dbConfig } = require('config.json');
module.exports = db = {};
initialize();
async function initialize() {
const dialect = 'mssql';
const host = dbConfig.server;
const { userName, password } = dbConfig.authentication.options;
// create db if it doesn't already exist
await ensureDbExists(dbName);
// connect to db
const sequelize = new Sequelize(dbName, userName, password, { host, dialect });
// init models and add them to the exported db object
db.User = require('../users/user.model')(sequelize);
// sync all models with database
await sequelize.sync({ alter: true });
}
async function ensureDbExists(dbName) {
return new Promise((resolve, reject) => {
const connection = new tedious.Connection(dbConfig);
connection.connect((err) => {
if (err) {
console.error(err);
reject(`Connection Failed: ${err.message}`);
}
const createDbQuery = `IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '${dbName}') CREATE DATABASE [${dbName}];`;
const request = new tedious.Request(createDbQuery, (err) => {
if (err) {
console.error(err);
reject(`Create DB Query Failed: ${err.message}`);
}
// query executed successfully
resolve();
});
connection.execSql(request);
});
});
}
Express.js中间件文件夹
中间件文件夹包含Express.js的中间件功能,可以被应用程序中的不同路线/功能使用。
授权中间件
路径。/_middleware/authorize.js
授权中间件可以被添加到任何路由中,以限制认证用户对路由的访问。它被用户控制器用来限制对用户CRUD路由的访问。
authorize函数返回一个包含两个中间件函数的数组。
- 第一个(
jwt({ ... }))通过验证http请求的 "授权 "头中的JWT访问令牌来验证请求。认证成功后,一个user对象被附加到req对象上,该对象包含来自JWT令牌的数据,在本例中包括令牌主题('sub')属性中的用户ID。 - 第二部分通过检查认证的用户是否仍然存在来授权请求,并将用户对象附加到请求中,以便控制器功能可以访问它。
如果认证或授权失败,则返回一个401 Unauthorized 响应。
const jwt = require('express-jwt');
const { secret } = require('config.json');
const db = require('_helpers/db');
module.exports = authorize;
function authorize() {
return [
// authenticate JWT token and attach decoded token to request as req.user
jwt({ secret, algorithms: ['HS256'] }),
// attach full user record to request object
async (req, res, next) => {
// get user with id from token 'sub' (subject) property
const user = await db.User.findByPk(req.user.sub);
// check user still exists
if (!user)
return res.status(401).json({ message: 'Unauthorized' });
// authorization successful
req.user = user.get();
next();
}
];
}
全局错误处理中间件
路径。/_middleware/error-handler.js
全局错误处理程序用于捕捉所有的错误,并消除整个应用程序中重复的错误处理代码的需要。它在主server.js文件中被配置为中间件。
根据惯例,'string' 类型的错误被视为自定义(应用程序特定)错误,这简化了抛出自定义错误的代码,因为只需要抛出一个字符串(例如:throw 'Invalid token' )。此外,如果自定义错误以'not found' 结尾,将返回404响应代码,否则将返回标准400响应。请参阅用户服务,了解由api抛出的自定义错误的一些例子,错误在每个路由的用户控制器中被捕获并传递给next(err) ,后者将它们传递给这个全局错误处理程序。
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
switch (true) {
case typeof err === 'string':
// custom application error
const is404 = err.toLowerCase().endsWith('not found');
const statusCode = is404 ? 404 : 400;
return res.status(statusCode).json({ message: err });
case err.name === 'UnauthorizedError':
// jwt authentication error
return res.status(401).json({ message: 'Unauthorized' });
default:
return res.status(500).json({ message: err.message });
}
}
验证请求中间件
路径。/_middleware/validate-request.js
验证请求的中间件函数根据Joi模式对象验证请求的主体。
它由控制器中的模式中间件函数使用,以验证请求与特定路由的模式(例如,authenticateSchema 在用户控制器)。
module.exports = validateRequest;
function validateRequest(req, next, schema) {
const options = {
abortEarly: false, // include all errors
allowUnknown: true, // ignore unknown props
stripUnknown: true // remove unknown props
};
const { error, value } = schema.validate(req.body, options);
if (error) {
next(`Validation error: ${error.details.map(x => x.message).join(', ')}`);
} else {
req.body = value;
next();
}
}
用户文件夹
用户文件夹包含所有特定于api的用户功能的代码。
序列化用户模型
路径。/users/user.model.js
用户模型使用Sequelize来定义SQL Server数据库中Users 表的模式。导出的Sequelize模型对象给出了在MSSQL中对用户进行CRUD(创建、读取、更新、删除)操作的完整权限,请看下面的用户服务的使用例子(通过db helper)。
defaultScope 将模型配置为默认从查询结果中排除密码哈希值。withHash 范围可用于查询用户并在结果中包括密码哈希字段。
const { DataTypes } = require('sequelize');
module.exports = model;
function model(sequelize) {
const attributes = {
firstName: { type: DataTypes.STRING, allowNull: false },
lastName: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false },
hash: { type: DataTypes.STRING, allowNull: false }
};
const options = {
defaultScope: {
// exclude hash by default
attributes: { exclude: ['hash'] }
},
scopes: {
// include hash with this scope
withHash: { attributes: {}, }
}
};
return sequelize.define('User', attributes, options);
}
用户服务
路径。/users/user.service.js
用户服务包含了node api中用户认证和管理的核心业务逻辑,它封装了与sequelize用户模型的所有交互,并暴露了一套简单的方法,这些方法被用户控制器使用。
文件的顶部包含了导出的服务对象,只有方法名称,以便于一目了然地看到所有的方法,文件的其余部分包含了每个服务方法的实现函数,然后是本地辅助函数。
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { secret } = require('config.json');
const db = require('_helpers/db');
module.exports = {
authenticate,
getAll,
getById,
create,
update,
delete: _delete
};
async function authenticate({ username, password }) {
const user = await db.User.scope('withHash').findOne({ where: { username } });
if (!user || !(await bcrypt.compare(password, user.hash)))
throw 'Username or password is incorrect';
// authentication successful
const token = jwt.sign({ sub: user.id }, secret, { expiresIn: '7d' });
return { ...omitHash(user.get()), token };
}
async function getAll() {
return await db.User.findAll();
}
async function getById(id) {
return await getUser(id);
}
async function create(params) {
// validate
if (await db.User.findOne({ where: { username: params.username } })) {
throw 'Username "' + params.username + '" is already taken';
}
// hash password
if (params.password) {
params.hash = await bcrypt.hash(params.password, 10);
}
// save user
await db.User.create(params);
}
async function update(id, params) {
const user = await getUser(id);
// validate
const usernameChanged = params.username && user.username !== params.username;
if (usernameChanged && await db.User.findOne({ where: { username: params.username } })) {
throw 'Username "' + params.username + '" is already taken';
}
// hash password if it was entered
if (params.password) {
params.hash = await bcrypt.hash(params.password, 10);
}
// copy params to user and save
Object.assign(user, params);
await user.save();
return omitHash(user.get());
}
async function _delete(id) {
const user = await getUser(id);
await user.destroy();
}
// helper functions
async function getUser(id) {
const user = await db.User.findByPk(id);
if (!user) throw 'User not found';
return user;
}
function omitHash(user) {
const { hash, ...userWithoutHash } = user;
return userWithoutHash;
}
Express.js用户控制器
路径。/users/users.controller.js
users控制器定义了api的所有/users 路由,路由定义被分组在文件的顶部,执行函数在下面。该控制器被绑定到主server.js文件中的/users 路径。
需要授权的路由包括中间件函数authorize() ,授权逻辑位于authorize中间件中。
需要模式验证的路由包括一个中间件函数,其命名规则为Schema (例如:authenticateSchema )。每个模式验证函数使用Joi库为请求主体定义一个模式,并调用validateRequest(req, next, schema) ,以确保请求主体是有效的。如果验证成功,请求将继续到下一个中间件函数(路由函数),否则将返回一个错误,说明验证失败的原因。关于Joi模式验证的更多信息,见www.npmjs.com/package/joi…
Express是api使用的网络服务器,它是Node.js最流行的网络应用框架之一。更多信息见expressjs.com/。
const express = require('express');
const router = express.Router();
const Joi = require('joi');
const validateRequest = require('_middleware/validate-request');
const authorize = require('_middleware/authorize')
const userService = require('./user.service');
// routes
router.post('/authenticate', authenticateSchema, authenticate);
router.post('/register', registerSchema, register);
router.get('/', authorize(), getAll);
router.get('/current', authorize(), getCurrent);
router.get('/:id', authorize(), getById);
router.put('/:id', authorize(), updateSchema, update);
router.delete('/:id', authorize(), _delete);
module.exports = router;
function authenticateSchema(req, res, next) {
const schema = Joi.object({
username: Joi.string().required(),
password: Joi.string().required()
});
validateRequest(req, next, schema);
}
function authenticate(req, res, next) {
userService.authenticate(req.body)
.then(user => res.json(user))
.catch(next);
}
function registerSchema(req, res, next) {
const schema = Joi.object({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
username: Joi.string().required(),
password: Joi.string().min(6).required()
});
validateRequest(req, next, schema);
}
function register(req, res, next) {
userService.create(req.body)
.then(() => res.json({ message: 'Registration successful' }))
.catch(next);
}
function getAll(req, res, next) {
userService.getAll()
.then(users => res.json(users))
.catch(next);
}
function getCurrent(req, res, next) {
res.json(req.user);
}
function getById(req, res, next) {
userService.getById(req.params.id)
.then(user => res.json(user))
.catch(next);
}
function updateSchema(req, res, next) {
const schema = Joi.object({
firstName: Joi.string().empty(''),
lastName: Joi.string().empty(''),
username: Joi.string().empty(''),
password: Joi.string().min(6).empty('')
});
validateRequest(req, next, schema);
}
function update(req, res, next) {
userService.update(req.params.id, req.body)
.then(user => res.json(user))
.catch(next);
}
function _delete(req, res, next) {
userService.delete(req.params.id)
.then(() => res.json({ message: 'User deleted successfully' }))
.catch(next);
}
Api配置
API配置文件包含auth API的配置数据,它包括MSSQL服务器的dbConfig 连接选项。dbName 属性是数据库的名称,该数据库在启动时由API在数据库封装器中自动创建。以及用于签署和验证JWT令牌的secret 。
重要的是:secret 属性用于签署和验证JWT令牌以进行身份验证,用你自己的随机字符串来改变它,以确保没有其他人可以用相同的秘密生成JWT,以获得对你的api的未授权访问。一个快速而简单的方法是将几个GUID连在一起,组成一个长的随机字符串(例如,从www.guidgenerator.com/)。
{
"dbName": "node-mssql-registration-login-api",
"dbConfig": {
"server": "localhost",
"options": {
"port": 1433,
"trustServerCertificate": true
},
"authentication": {
"type": "default",
"options": {
"userName": "sa",
"password": ""
}
}
},
"secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
}
Package.json
package.json文件包含项目配置信息,包括当你运行npm install ,就会安装的软件包dependencies 。
scripts 部分包含通过运行命令执行的脚本npm run