一、先预习/复习下Node.js相关内容

1.简介

简单的说 Node.js 就是运行在服务端的 JavaScript 环境
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

2.安装

Windows下载安装:https://nodejs.org/en/download/

Mac下载安装:brew install node

安装ts-node
npm install ts-node

由于是基于typescript,需安装相关依赖

npm install @types/node
npm install typescript
npm install tslint

3.创建一个Node.js应用

项目的根目录的src目录下创建一个叫 server.ts 的文件:

server.ts

import express from 'express';

const app = express();
 
app.get('/', (req, res) => {
    res.send('Hello World');
});

app.listen(8888, () => {
    var host = server.address().address;
    var port = server.address().port;
    
    console.log("应用实例,访问地址为 http://%s:%s", host, port)
});

使用ts-node执行
npx ts-node ./src/server.ts

浏览器打开127.0.0.1:8888

4.路由

我们要为路由提供请求的 URL 和其他需要的 GET 及 POST 参数,随后路由需要根据这些数据来执行相应的代码。和前端路由不同的是,NodeJs路由主要是处理后端业务逻辑并且返回包含处理后数据的响应体。

// server.ts

import express from 'express';

const app = express();
 
// 响应get请求
app.get('/get_user', (req, res) => {
    const reqQuery = {
        "first_name": req.query.first_name,
        "last_name":  req.query.last_name
    };
    const resBody = reqQuery;
    
    res.end(JSON.stringify(resBody));
});

// 响应post请求,参数为json
app.post('/list_user', (req, res) => {

    let reqBody: any|string = '';
    
    // 获取请求体数据
    req.on('data', chunk => {
      reqBody += chunk;
    });
    
    // 请求数据获取完成
    req.on('end', () => {
      reqBody = JSON.parse(reqBody || '{}');
      const resBody = reqBody;
      
      res.send(resBody);
    });
});

// 对页面 abcd, abxcd, ab123cd, 等响应 GET 请求
app.get('/ab*cd', function(req, res) {   
   console.log("/ab*cd GET 请求");
   
   res.send('正则匹配');
})

app.listen(8081);

执行npx ts-node ./src/server.ts

get请求

post请求

路由字符串匹配

5.上传文件 Multer

Multer是用于处理multipart/form-data的node.js中间件,主要用于上传文件。它是在busboy之上编写的,以实现最大效率。

server.ts

import express from 'express';
import fs from 'fs';
import bodyParser from 'bodyParser';
import multer from 'multer';
 
const app = express();

app.use(multer({
    // 上传文件默认存放路径
    dest: '/tmp/'
})
// 第一个参数是字段名,第二个参数是最多上传数
.array('attachment', 5));

app.post('/file_upload', (req, res) => {

    const reqFile = req.files[0];
    
    // 最终存放文件位置
    const desFile = 'C://Desktop/' + reqFile.originalname;

    fs.readFile( reqFile.path, (readErr, data) => {
        fs.writeFile(des_file, data, function (writeErr) {

            if (writeErr) {
                console.log(writeErr);
            } else {
                resBody = {
                    message:  'File uploaded successfully', 
                    filename: reqFile.originalname
                };
            }
    
            console.log(resBody);
            res.send(resBody);
       });
   });

});

app.listen(8081);

执行npx ts-node ./src/server.ts

6.cookies

server.ts

import express from 'express';
import cookieParser from 'cookie-parser';
import util from 'util';
 
var app = express();

// 使用cookie解析中间件
app.use(cookieParser());

app.post('/', function(req, res) {
    res.send({
        // inspect方法用于解析cookies
        cookies: util.inspect(req.cookies),
        code: 200
    });
});

app.listen(8081);

执行npx ts-node ./src/server.ts

7.express框架

Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

npm install express

以下几个重要的模块是需要与 express 框架一起安装的:

body-parser - node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。

cookie-parser - 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。

multer - node.js 中间件,用于处理 enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。

npm install body-parser 
npm install cookie-parser 
npm install multer 

二、开始搭建项目结构

1.整体目录结构

2.server.ts - 服务启动入口

// 第三方模块
import bodyParser from 'body-parser';
import express from 'express';
import { NextFunction, Request, Response } from 'express'; // express 申明文件定义的类型

// 自定义配置
import systemConfig from './config';

import loadRouters from './routers/routers';
import connectDatabase from './utils/database';

let app = express();

app = loadRouters(app);

// 连接数据库
connectDatabase.then(res => {
  console.log('in database: isOpenDatabase', res);
});

// 处理 post 请求的请求体,限制大小最多为 20 兆
app.use(bodyParser.urlencoded({ limit: '20mb', extended: true }));
app.use(bodyParser.json({ limit: '20mb' }));

// error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  return res.sendStatus(500);
});

app.listen(systemConfig.port, () => {
  console.log(`the server is start at port ${systemConfig.port}`);
});

export default app;

3.config - 定义基础配置

定义基础的数据变量和常量

例如 systemConfig.ts

import { SystemConfig } from '../types/systemConfig';

const config: SystemConfig = {
  port: 8081,
  database: 'mongodb://localhost:27017/test',
  uploadPath: '/Users/linli/Desktop/',
  jsonPath: '/Users/linli/Desktop/'
};

export default config;

4.types - 类型声明接口

用于声明数据类型

例如 systemConfig.d.ts

export interface SystemConfig {
  // 服务器端口号
  port: number;

  // 数据库地址
  database: string;

  // 上传地址
  uploadPath: string;

  // json存放地址
  jsonPath: string;
}

5.routers - 后端路由

不同的路由对应不同控制器处理方法

routers.ts 主路由

import userRouter from './user.router';
// import xxxRouter from './xxx.router';

/**
 * 路由加载工厂函数
 * @param app any
 * @returns app
 */
const loadRouters = (app: any) => {

  // 加载 用户 路由
  app.use(userRouter);

  return app;
};

export default loadRouters;

userRouter.ts 用户路由

import express from 'express';
import cookieParse from 'cookie-parser';

import UserController from '../controllers/user.controller';

const userRouter = express.Router();

userRouter.use(cookieParse());
userRouter.use(express.urlencoded({ extended: false }));

// 创建用户
userRouter.post('/createUser.vm', UserController.createUser);

// 删除用户
userRouter.post('/deleteUser.vm', UserController.deleteUser);

// 查询用户
userRouter.post('/getUser.vm', UserController.getUser);

// 查询用户列表
userRouter.post('/getUserList.vm', UserController.getUserList);

// 修改用户
userRouter.post('/updateUser.vm', UserController.updateUser);

export default userRouter;

6.controllers - 控制器

用于处理复杂业务逻辑

例如 user.controller.ts

import util from 'util';
import crypto from 'crypto';

import { BaseDao } from '../utils/base-dao';
import userModel from '../models/user.model';

const userDao = new BaseDao(userModel);

const UserController = {

  /**
   * 创建用户
   * @param req 请求对象
   * @param res 响应对象
   */
  createUser(req: any, res: any) {

    console.log('in createUser: req.body', req.body);

    console.log('Cookies: ' + util.inspect(req.cookies));

    // 请求体对象
    let reqBody: any|string = '';

    // 获取请求体数据
    req.on('data', (chunk: any) => {
      reqBody += chunk;
    });

    // 请求数据获取完成
    req.on('end', () => {

      // 解析请求体
      reqBody = JSON.parse(reqBody || '{}');

      // 数据库中创建用户
      userDao.create([
        {
          username: reqBody.username,
          password: crypto.createHash('sha256').update(reqBody.password).digest('hex')
        }
      ], (result: any) => {

        res.send(result);

      });

    });

  },

  /**
   * 获取用户
   * @param req 请求对象
   * @param res 响应对象
   */
  getUser(req: any, res: any) {

    console.log('in userRouter queryUser req:', req);

    // 请求体对象
    let reqBody: any|string = '';

    // 获取请求体数据
    req.on('data', (chunk: any) => {
      reqBody += chunk;
    });

    // 请求数据获取完成
    req.on('end', () => {

      // 解析请求体
      reqBody = JSON.parse(reqBody || '{}');

      console.log('in createUser: req.body', reqBody);

      // 从数据库中查找用户
      userDao.query(reqBody, (result: any) => {

        res.send(result);

      });

    });

  },

  /**
   * 获取用户列表
   * @param req 请求对象
   * @param res 响应对象
   */
  getUserList(req: any, res: any) {

    // 请求体对象
    let reqBody: any|string = '';

    // 获取请求体数据
    req.on('data', (chunk: any) => {
      reqBody += chunk;
    });

    // 请求数据获取完成
    req.on('end', () => {

      // 解析请求体
      reqBody = JSON.parse(reqBody || '{}');

      console.log('in createUser: req.body', reqBody);

      // 数据库中获取用户列表
      userDao.getAll((result: any) => {

        res.send(result);

      });

    });

  },

  /**
   * 更新对象
   * @param req 请求对象
   * @param res 响应对象
   */
  updateUser(req: any, res: any) {

    // 请求体对象
    let reqBody: any|string = '';

    // 获取请求体数据
    req.on('data', (chunk: any) => {
      reqBody += chunk;
    });

    // 请求数据获取完成
    req.on('end', () => {

      // 解析请求体
      reqBody = JSON.parse(reqBody || '{}');

      reqBody.password = crypto.createHash('sha256').update(reqBody.password).digest('hex');

      console.log('in createUser: req.body', reqBody);

      // 数据库中更新用户
      userDao.update({
          _id: reqBody.id
        },
        {
          $set: reqBody
        }, {}, (result: any) => {

          res.send(result);

        });

    });

  },

  /**
   * 删除对象
   * @param req 请求对象
   * @param res 响应对象
   */
  deleteUser(req: any, res: any) {

    // 请求体对象
    let reqBody: any|string = '';

    // 获取请求体数据
    req.on('data', (chunk: any) => {
      reqBody += chunk;
    });

    // 请求数据获取完成
    req.on('end', () => {

      // 解析请求体
      reqBody = JSON.parse(reqBody || '{}');

      console.log('in createUser: req.body', reqBody);

      // 数据库中删除用户
      userDao.delete(reqBody, (result: any) => {

        res.send(result);

      });

    });

  }

};

export default UserController;

7.util - 工具类

数据库实例封装

例如 database.ts

import mongoose from 'mongoose';

// 自定义配置
import systemConfig from '../config';

export default new Promise((resolve, reject) => {

  // 连接数据库
  mongoose.connect(systemConfig.database, { useCreateIndex: true, useNewUrlParser: true });

  mongoose.connection.once('open', error => {

    if (!error) {
      console.log('数据库连接成功');
      resolve(true);
    } else {
      console.log('数据库连接失败', error);
      reject(error);
    }

  });
});

数据层基础方法构造函数

例如 base-dao.ts

/**
 * Created by lilin on 19-11-19
 */

export class BaseDao {
  model: any;

  constructor(newModel: any) {
    this.model = newModel;
  }

  /**
   * 创建
   * @param docs any[] 新建对象集合
   * @param callback
   */
  create(docs: any[], callback: any) {

    this.model.create(docs, (error: any) => {
      if (error) {
        return callback({data: null, code: 500});
      }

      return callback({data: 'success', code: 200});
    });

  }

  /**
   * 查询
   * @param params 查询条件
   * @param callback
   */
  query(conditions: any, callback: any) {

    this.model.findOne(conditions, (error: any, result: any) => {

      if (error) {
        return callback({data: null, code: 500});
      }

      return callback({data: result, code: 200});
    });

  }

  /**
   * 查询所有数据
   * @param callback
   */
  getAll(callback: any) {
    this.model.find({}, (error: any, result: any) => {

      if (error) {
        return callback({data: null, code: 500});
      }

      return callback({data: result, code: 200});

    });
  }

  /**
   * 删除
   * @param params 删除条件
   * @param callback
   */
  delete(conditions: any, callback: any) {

    this.model.remove(conditions, (error: any) => {

      if (error) {
        return callback({data: null, code: 500});
      }

      return callback({data: 'success', code: 200});

    });

  }

  /**
   * 更新
   * @param conditions 更新条件
   * @param doc 更新后的对象
   * @param options 更新选项
   * @param callback 回调
   */
  update(conditions: any, doc: any, options: any, callback: any) {

    this.model.update(conditions, doc, options, (error: any) => {

      if (error) {
        return callback({data: null, code: 500});
      }
      return callback({data: 'success', code: 200});

    });

  }

}

8.models - mongoose 模型

Models 是从 Schema 编译来的构造函数。 它们的实例就代表着可以从数据库保存和读取的 documents。 从数据库创建和读取 document 的所有操作都是通过 model 进行的。

不了解mongodb和mongoose的同学可以看看官网文档,本篇文章主要讲架构

例如 user.model.ts

import { Schema, model } from 'mongoose';

const userSchema = new Schema({
  username: {
    type: String,
    unique: true,
    required: true
  },
  password: {
    type: String,
    required: true
  }
});

const userModel = model('user', userSchema);

export default userModel;

9. package.json - npm依赖和构建脚本

{
    "name": "template-node-ts",
    "version": "1.0.0",
    "scripts": {
        "serve": "ts-node src/server.ts",
        "dev": "ts-node-dev src/server.ts"
    },
    "dependencies": {
        "@types/cookie-parser": "^1.4.2",
        "@types/express": "^4.17.2",
        "@types/mongoose": "^5.5.32",
        "@types/multer": "^1.3.10",
        "cookie-parser": "^1.4.4",
        "express": "^4.17.1",
        "mongoose": "^5.7.11",
        "multer": "^1.4.2"
    },
    "devDependencies": {
        "@types/jest": "^24.0.13",
        "@types/node": "^12.0.2",
        "@types/supertest": "^2.0.7",
        "jest": "^24.8.0",
        "supertest": "^4.0.2",
        "ts-jest": "^24.0.2",
        "ts-node": "^8.5.2",
        "ts-node-dev": "^1.0.0-pre.39",
        "tslint": "^5.17.0",
        "typescript": "^3.7.2"
    }
}

10.tsconfig.ts - ts配置

{
    "compilerOptions": {
        /* Basic Options */
        "target": "es6",                     /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */

        //  ts-node 目前不支持es2015的的模块机制
        //  https://github.com/TypeStrong/ts-node/issues/313#issuecomment-343698812
        "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
        
        /* Strict Type-Checking Options */
        "strict": true,                           /* Enable all strict type-checking options. */
        "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */

        /* Module Resolution Options */
        "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
        "baseUrl": ".",                       /* Base directory to resolve non-absolute module names. */
        
        "paths": {
            "*": [
                "node_modules/*",
                "src/types/*"
            ]
        },
        
        "esModuleInterop": true,                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
        
        /* Experimental Options */
        "experimentalDecorators": true       /* Enables experimental support for ES7 decorators. */
    },
    "exclude": ["node_modules"]
}

三、项目演示

搭建好项目结构后,运行
npx node-ts ./src/server.ts

也可以通过tsc打包后,在生成的dist目录下执行node server.js

创建用户

查询用户

删除用户

更新用户

四、总结

本篇文章主要讲解typescript在Node.js原型工程中的项目架构,如果有不明确或者有错误的地方还请万能的网友指点,如果觉得文章对你有帮助,希望点个赞鼓励下哦,最后,在这特殊的时期祝愿大家平平安安、身体健康,闲的没事可以多逛逛掘金,不要着急出门,武汉加油,中国加油!


李霖
31 声望1 粉丝