ts-node笔记

1,286 阅读5分钟

ts-node项目

该简单项目搭建简单node后端环境,增删改查接口,并接口测试,最后发布到docker

创建项目

mkdir tsnode
cd tsnode
npm init -y 生成默认package.json
npx tsconfig.json 快速创建node环境

安装依赖

tnpm i express mysql2 morgan http-errors http-satus-codes -S

tnpm i cross-env typescript @types/express @types/morgan @types/http-errors ts-node-dev nodemon ts-node nyc mocha @types-mocha chai @types/chai chai-http -D
  • morgan HTTP request logger middleware for node.js 打印日志
  • http-errors Create HTTP errors for Express, Koa, Connect, etc. with ease.
  • http-satus-codes http常用状态码
  • ts-node-dev 运行ts版node代码,通过--respawn监控源文件变化
  • ts-node node不支持import,export,靠它来支持
  • nyc 用来生成测试覆盖率文件
  • mocha 测试框架
  • chai 断言库

package.json

  "scripts": {
    "start": "cross-env PORT=8000 ts-node-dev --respawn ./src/bin/www.ts",
    "dev": "cross-env PORT=8000 nodemon --exec ts-node --files ./src/bin/www.ts",
    "test": "cross-env PORT=8000 TS_NODE_FILES=true mocha -r ts-node/register src/tests/*.spec.ts --exit",
    "coverage": "nyc --reporter lcov npm run test"
  },

其中dev,设置环境变量 9000,用nodemon启动 执行bin下面的文件

项目目录结构

app.ts

import createError from 'http-errors';
import express, { Express, Request, Response, NextFunction } from 'express';
import { NOT_FOUND, INTERNAL_SERVER_ERROR } from 'http-status-codes';
// import logger from 'morgan';
import indexRouter from './routes/index'; // 引入routers中的index.ts
import usersRouter from './routes/user'; // 引入routers中的user.ts
const app: Express = express();
// app.use(logger('dev')); // 打印请求日志 dev是格式,short,tiny
app.use(express.json()); // 解析JSON格式的请求体,实际是调的 bodyParser.json
app.use(express.urlencoded({ extended: false })); // 解析表单形式的请求 bodyParser.urlencoded

app.use('/', indexRouter);
app.use('/users', usersRouter);

app.use( (_req: Request, _res: Response, next: NextFunction) => {
    next(createError(NOT_FOUND)); // 给错误处理中间件, NOT_FOUND 传递给下面的error
})

app.use((error: any, _req: Request, res: Response, _next: NextFunction) => {
    res.status(error.status || INTERNAL_SERVER_ERROR);
    res.json({
        success: false,
        error
    })
})

export default app;

www.ts

// 启动服务
import app from '../app'; // 引入
import http from 'http';

const port = process.env.PORT || 8000;
const server = http.createServer(app);

server.listen(port);

server.on('error', (error) => {
    console.error(error); // 监听错误
})
server.on('listening', () => {
    console.log('listening on ' + port); // 监听启动
})

数据库mysql 和 sequelize 定义模型

sequelize.ts

// Sequelize 是一个基于 promise 的 Node.js ORM,(像操作js一样操作) 目前支持 Postgresql, MySQL, SQLite 和 Microsoft SQL Server.
// 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能.

import { Sequelize } from 'sequelize';
const sequelize = new Sequelize('api', 'root', 'root', { // database, username, password
    host: 'localhost', // 本地用localhost,后期docker改成db
    dialect: 'mysql', // 使用哪种数据库
    logging: false // 是否打印日志
})
export {
    sequelize
}

user.ts

import { Model, DataTypes } from 'sequelize';
import { sequelize } from './sequelize';
/**
 * 每个数据库表对应一个模型Model
 * Model封装了针对数据库的各种操作
 */
class User extends Model {}

User.init({
    username: DataTypes.STRING,
    password: DataTypes.STRING,
    email: DataTypes.STRING
}, {sequelize, modelName: 'user'}); // user表,字段username,pwd,email
// 同步到数据库中,第一次可以使用
// sequelize.sync().then(() => {
//     User.create({
//         username: 'jeffywin',
//         password: '123456',
//         email: 'jeffywin@xxx.com'
//     })
// }).then((result: User) => {
//     console.log('result',result);
// });
export {
    User
}

routers user.ts

import express, {Response, Router, Request, NextFunction} from 'express';
import createError from 'http-errors';
import { INTERNAL_SERVER_ERROR } from 'http-status-codes'; // 500错误
import { User } from '../model'; // sequlize中的模型
let router: Router = express.Router();

// 获取所有用户 GET /users  localhost:8000/user
router.get('/', async(_req: Request, res: Response) => {
    let users = await User.findAll(); // 找到数据库中所有用户
    res.json({
        success: true,
        data: users
    })
})
// 获取某个用户 GET /users/:id
router.get('/:id', async(req:Request, res: Response, next: NextFunction) => {
    try {
        let user = await User.findByPk(req.params.id); // findPrimayKey
        if(user) {
            res.json({
                success: true,
                data: user
            })
        }
    } catch (error) {
        next(createError(INTERNAL_SERVER_ERROR))
    }
})

// 添加一个用户 POST /users
router.post('/', async(req: Request, res: Response, next: NextFunction) => {
    try {
        let user = req.body;
        user = await User.create(user);
        res.json({
            success: true,
            data: user
        })
    } catch (error) {
        next(createError(INTERNAL_SERVER_ERROR));
    }
})
// 更新一个用户 PUT /users/:id
router.put('/:id', async(req: Request, res: Response, next: NextFunction) => {
    try {
        let user = await User.findByPk(req.params.id);
        let update = req.body; // 拿到更新的内容
        user = await user.update(update);
        res.json({
            success: true,
            data: user
        })
    } catch (error) {
        next(createError(INTERNAL_SERVER_ERROR));
    }
})

// 删除一个用户 DELETE /users/:id
router.delete('/:id', async(req: Request, res: Response, next: NextFunction) => {
    try {
        let id = req.params.id;
        let user = await User.findByPk(id);
        user = await user.destroy(); // 删除用户
        res.json({
            success: true,
            data: user
        })
    } catch (error) {
        next(createError(INTERNAL_SERVER_ERROR));
    }
})

export default router;

测试

helper.spec.ts

import chai from 'chai';
import chaiHttp from 'chai-http'; // 插件
import {sequelize, User} from '../model';

chai.use(chaiHttp);

// 在所有单元测试之前进行
before(async () => {
    await sequelize.sync(); // 让模型里的定义和数据库定义同步
})
// 在所有单元测试之前进行
after(async () => {
})

// 在每个单元测试之前进行
beforeEach(async () => {
    await User.truncate(); // 清空表
})
// 在每个单元测试之后进行
afterEach(async () => {
    await User.truncate();
})

index.spec.ts

import app from '../app';
import chai, {expect} from 'chai';

describe('测试用户的restful接口', () => {
    it('POST /users 添加用户', async () => {
      let result = await chai.request(app)
        .post('/users')
        .set('Content-Type', 'application/json')
        .send({username: 'jeffywin', password: '12345', email: 'jeffywin@126.com'});
      expect(result).to.have.status(200);
      expect(result.body).to.have.property('success');
      expect(result.body.success).to.equal(true);
      expect(result.body).to.have.property('data');
      expect(result.body.data.id).to.equal(1);
    })

    it('GET /users 查询用户', async () => {
     // 由于数据库清空,先添加一条用户
      await chai.request(app)
        .post('/users')
        .set('Content-Type', 'application/json')
        .send({username: 'zhangsan', password: '12345', email: 'jeffywin@126.com'});

      await chai.request(app)
        .post('/users')
        .set('Content-Type', 'application/json')
        .send({username: 'lisi', password: '12345', email: 'jeffywin@126.com'});
  
      let result = await chai.request(app)
        .get('/users')
      expect(result).to.have.status(200);
      expect(result.body).to.have.property('success');
      expect(result.body.success).to.equal(true);
      expect(result.body).to.have.property('data');
      expect(result.body.data).to.have.lengthOf(2);
    })

    it('DELETE /users/:id 删除用户', async () => {
      // 删除之前先添加一个用户,再删除
      let addResult = await chai.request(app)
        .post('/users')
        .set('Content-Type', 'application/json')
        .send({username: 'lisi', password: '12345', email: 'jeffywin@126.com'});

      await chai.request(app)
        .post('/users')
        .set('Content-Type', 'application/json')
        .send({username: 'zhangsan', password: '12345', email: 'jeffywin@126.com'});

      await chai.request(app)
        .delete(`/users/${addResult.body.data.id}`)
      
      let result1 = await chai.request(app)
        .get('/users')
      expect(result1).to.have.status(200);
      expect(result1.body).to.have.property('success');
      expect(result1.body.success).to.equal(true);
      expect(result1.body).to.have.property('data');
      expect(result1.body.data).to.have.lengthOf(1);
    })

    it('PUT /users/:id 更新用户', async () => {
      // 先插入用户,然后查询用户,再expect
      let addResult = await chai.request(app)
      .post('/users')
      .set('Content-Type', 'application/json')
      .send({username: 'lisi', password: '12345', email: 'jeffywin@126.com'});
      let result = await chai.request(app)
        .put(`/users/${addResult.body.data.id}`)
        .set('Content-Type', 'application/json')
        .send({username: 'lisi22', password: '654321', email: 'jeffywin@126.com'});
      let result2 = await chai.request(app)
        .get(`/users/${result.body.data.id}`)
      expect(result2).to.have.status(200);
      expect(result2.body).to.have.property('success');
      expect(result2.body.success).to.equal(true);
      expect(result2.body).to.have.property('data');
      expect(result2.body.data.username).to.equal('lisi22');
      expect(result2.body.data.password).to.equal('654321');
    })    
})

最后 npm run coverage 查看测试覆盖率文件

docker

Docker是一个开放源代码软件项目,让应用程序部署在软件货柜下的工作可以自动化进行,借此在Linux操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制

准备工作

  • 1.购买云服务器
  • 2.本地能够访问云服务器,我用的是Core Shell

连上去后就是这个状态

  • 3.安装docker
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
  • 4.aliyun加速,不然下载包会非常慢,安装git
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://fwvjnv59.mirror.aliyuncs.com"]
}
EOF
# 重载所有修改过的配置文件
systemctl daemon-reload
systemctl restart docker

yum install git -y
  • 5.本地项目新建Dockerfile文件
FROM node
COPY . /api
WORKDIR /api
RUN yarn config set registry 'https://registry.npm.taobao.org'
RUN yarn
EXPOSE 8000
CMD npm start
  • 6.本地项目新建.dockerignore文件,忽略上传文件
.git
node_modules
package.lock.json
Dockerfile
.dockerignore
  • 7.云服务器安装docker-compose
curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
  • 8.提交本地项目至远程仓库,并在云服务器上拉取代码

  • 9.vim docker-compose.yml,注意格式问题,很容易报错

version: '2'
services:
 node:
  build:
    context: ./tsnode // 云服务器拉取的项目文件夹
    dockerfile: Dockerfile
  ports:
    - "8000:8000"  
  depends_on:
   - db
 db:
  image: mariadb // 数据库
  environment:
   MYSQL_ROOT_PASSWORD: "root"
   MYSQL_DATABASE: "restful"
  volumes:
    - db:/var/lib/mysql
volumes:
 db:
  driver: local

  • 10.启动docker-compose,三个命令
docker-compose build 打包代码
docker-compose up 运行
docker-compose up -d 后台运行

远程访问项目

到此增删改查都可以操作,一个简单的小项目完成