likes
comments
collection
share

从零开始搭建一个简易的本地Node服务端-ts基础版

作者站长头像
站长
· 阅读数 119

前言

本次的Node环境搭建主要是搭建一个Node+Koa+ts+mysql的服务端,node版本为@15.14.0

初始化

新建一个文件夹npm进行初始化

使用npm进行初始化命令(npm初始化比较方便,也可以用yarn初始化,但需要手动添加一些配置)

npm init -y

初始化成功之后得到一个package.json文件

使用yarn安装第三方库

安装koa和koa-router

yarn add koa koa-router

注意:使用ts的情况下,安装第三方库之后需要安装ts对应的类型检测提示

安装kao和koa-router的类型检测提示

yarn add --save-dev @types/koa @types/koa-router

安装完kao和kao-router之后,会得到一个node_modules文件夹和yarn.lock文件

安装ts热更新编译

yarn add typescript ts-node nodemon -D

安装完成初始化tsconfig.json文件

tsc --init

配置tsconfig文件,指定需要编译的目录范围

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  },
  // 指定需要编译的目录范围
  "include": [
    "src/**/*",
  ],
  // 忽略类型检查的目录
  "exclude": ["node_modules"]
}

配置package.json文件

  "scripts": {
    "build": "tsc",
    "start": "tsc dist/index.ts",
    "dev": "nodemon --watch src -e ts,tsx --exec ts-node src/index.ts"
  },

安装koa-body

yarn add koa-body

安装mysql

yarn add mysql

yarn add @types/mysql

服务器启动

在根目录下新建一个src文件夹,src下新建一个index.ts,这个index.ts文件就是我们的入口文件

在index文件里我们主要导入koa,设置请求配置,挂载路由以及监听端口

/*
 * @Author: chenyt
 * @Date: 2023-11-04 10:05:00
 * @Description: 
 */
import koa from 'koa';
import koaBody from 'koa-body';


const app = new koa();

// 使用中间件处理 post 传参 和上传图片
app.use(koaBody({
    multipart: true,
    formidable: {
        //   maxFileSize: config.uploadImgSize
    }
}));

// 先统一设置请求配置 => 跨域,请求头信息...
app.use(async (ctx, next) => {
    /** 请求路径 */
    // const path = ctx.request.path;

    console.log("--------------------------");
    console.count("request count");

    const { origin, referer } = ctx.headers;

    // const domain = utils.getDomain(referer || "");
    // console.log("referer domain >>", domain);
    // 如果是 允许访问的域名源 ,则给它设置跨域访问和正常的请求头配置
    // if (domain && config.origins.includes(domain)) {
        ctx.set({
            // "Access-Control-Allow-Origin": domain,
            "Access-Control-Allow-Origin": "*", // 开启跨域,一般用于调试环境,正式环境设置指定 ip 或者指定域名
            // "Content-Type": "application/json",
            // "Access-Control-Allow-Credentials": "true",
            // "Access-Control-Allow-Methods": "OPTIONS, GET, PUT, POST, DELETE",
            "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
            // "X-Powered-By": "3.2.1",
            // "Content-Security-Policy": `script-src "self"` // 只允许页面`script`引入自身域名的地址
        });
    // }

    // 如果前端设置了 XHR.setRequestHeader("Content-Type", "application/json")
    // ctx.set 就必须携带 "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization" 
    // 如果前端设置了 XHR.setRequestHeader("Authorization", "xxxx") 那对应的字段就是 Authorization
    // 并且这里要转换一下状态码
    // console.log(ctx.request.method);
    if (ctx.request.method === "OPTIONS") {
        ctx.response.status = 200;
    }

    // const hasPath = router.stack.some(item => item.path == path);
    // // 判断是否 404
    // if (path != "/" && !hasPath) {
    //     return ctx.body = "<h1 style="text-align: center; line-height: 40px; font-size: 24px; color: tomato">404:访问的页面(路径)不存在</h1>";
    // }

    try {
        console.log("成功");
        
        await next();
    } catch (err: any) {
        console.log("Error: " + err);
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.body = {
            message: err.message
        }
    }
});
app.on("error", (err, ctx) => {
    console.log(`\x1B[91m server error !!!!!!!!!!!!! \x1B[0m`, err, ctx);
})

app.listen(3000, () => {
    // for (let i = 0; i < 100; i++) {
    //     console.log(`\x1B[${i}m 颜色 \x1B[0m`, i);
    // }
    console.log("服务器启动完成:");
})

运行yarn dev可以看到控制台提示服务器启动完成

连接msyql

首先在src文件夹下新建几个文件夹,具体目录结构如下图:

从零开始搭建一个简易的本地Node服务端-ts基础版

接下来我们会在utils中新建一个mysql.ts用来连接mysql

但是在正式连接之前,我们需要先设置一些mysql的配置,在modules文件夹下新建一个Config.ts文件,然后将mysql需要用到的一些配置放到该文件中

// readonly 只读
class ModuleConfig {
    constructor() {

    }

    /** 数据库配置 */
    readonly db = {
        host: 'localhost',
        user: 'root',
        password: 'root',
        database: 'node-ts',
        port: 3306,
        maxLimit: 10,
    }

    /** 接口 */
    readonly apiPrefix = ''
}

/** 项目配置 */
const config = new ModuleConfig();
export default config;

在utils中新建mysql.ts文件,mysql文件用来封装一些mysql的操作,以及对mysql的返回结果进行一些处理

import * as mysql from 'mysql';
import config from '../modules/Config';

// mysql查询结果
interface MsqlResult {
    state: number;
    results: any;
    msg: string;
    error: mysql.MysqlError | null;
    fields: Array<mysql.FieldInfo> | null;
}

// 数据库连接池
const pool = mysql.createPool({
    host: config.db.host,
    user: config.db.user,
    password: config.db.password,
    database: config.db.database,
    port: config.db.port
})

/**
 * 数据库操作
 */

export default function query(command: string, value?: Array<any>) {
    const result: MsqlResult = {
        state: 0,
        results: null,
        msg: "",
        error: null,
        fields: null
    }
    return new Promise<MsqlResult>((resolve, reject) => {
        pool.getConnection((error: any, connection) => {
            if(error) {
                result.error = error;
                result.msg = "数据库连接出错";
                resolve(result);
            } else {
                const callback: mysql.queryCallback = (error: any, result, fields) => {
                    connection.release();
                    if(error) {
                        result.error = error;
                        result.msg = "数据库操作出错";
                        resolve(result);
                    } else {
                        result.state = 1;
                        result.msg = "数据库操作成功"
                        result.results = result;
                        result.fields = fields;
                        resolve(result);
                    }
                }
                if(value) {
                    pool.query(command, value, callback);
                } else {
                    pool.query(command, callback);
                }
            }
        })
    })
}

接口开发

node的接口开发流程主要是,model文件夹建立数据模型,controllers文件夹进行业务处理,routes文件夹进行路由处理,最终在index文件中进行挂载。

下面我们用获取tags列表来进行举例: model文件夹的Tags.ts文件

/*
 * @Author: chenyt
 * @Date: 2023-11-04 11:47:08
 * @Description: 
 */
import query from '../utils/msyql';

async function all() {
    const res = await query(`SELECT * FROM tags`)
    return res.results;
}
module.exports = { all }

contorllers的tagsController.ts文件

const tag = require('../models/Tags.ts');

async function getTagsList(ctx: { body: { code: number; msg: string; data: any; }; }) {
    const tags = await tag.all();
    ctx.body = {
        code: 200,
        msg: '获取成功',
        data: tags
    }
}
module.exports = { getTagsList }

routes文件夹的tags.ts文件

import router from "./mian";
const tagController = require('../controllers/tagsController');

router.get('/tags', tagController.getTagsList);

在routes文件夹下建立一个main.ts文件,用于路由的处理

import Router = require("koa-router");
import config from "../modules/Config";

const router = new Router({
    prefix: config.apiPrefix
});

export default router;

在index文件下进行路由挂载

import koa from 'koa';
import koaBody from 'koa-body';
// import config from './modules/Config';
import router from './routes/mian';
// import tok
// import utils from './utils';
import "./routes/tags"


const app = new koa();

// 使用中间件处理 post 传参 和上传图片
app.use(koaBody({
    multipart: true,
    formidable: {
        //   maxFileSize: config.uploadImgSize
    }
}));

// 先统一设置请求配置 => 跨域,请求头信息...
app.use(async (ctx, next) => {
    /** 请求路径 */
    // const path = ctx.request.path;

    console.log("--------------------------");
    console.count("request count");

    const { origin, referer } = ctx.headers;

    // const domain = utils.getDomain(referer || "");
    // console.log("referer domain >>", domain);
    // 如果是 允许访问的域名源 ,则给它设置跨域访问和正常的请求头配置
    // if (domain && config.origins.includes(domain)) {
        ctx.set({
            // "Access-Control-Allow-Origin": domain,
            "Access-Control-Allow-Origin": "*", // 开启跨域,一般用于调试环境,正式环境设置指定 ip 或者指定域名
            // "Content-Type": "application/json",
            // "Access-Control-Allow-Credentials": "true",
            // "Access-Control-Allow-Methods": "OPTIONS, GET, PUT, POST, DELETE",
            "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
            // "X-Powered-By": "3.2.1",
            // "Content-Security-Policy": `script-src "self"` // 只允许页面`script`引入自身域名的地址
        });
    // }

    // 如果前端设置了 XHR.setRequestHeader("Content-Type", "application/json")
    // ctx.set 就必须携带 "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization" 
    // 如果前端设置了 XHR.setRequestHeader("Authorization", "xxxx") 那对应的字段就是 Authorization
    // 并且这里要转换一下状态码
    // console.log(ctx.request.method);
    if (ctx.request.method === "OPTIONS") {
        ctx.response.status = 200;
    }

    // const hasPath = router.stack.some(item => item.path == path);
    // // 判断是否 404
    // if (path != "/" && !hasPath) {
    //     return ctx.body = "<h1 style="text-align: center; line-height: 40px; font-size: 24px; color: tomato">404:访问的页面(路径)不存在</h1>";
    // }

    try {
        console.log("成功");
        
        await next();
    } catch (err: any) {
        console.log("Error: " + err);
        ctx.response.status = err.statusCode || err.status || 500;
        ctx.response.body = {
            message: err.message
        }
    }
});
// 路由挂载
app.use(router.routes())
app.on("error", (err, ctx) => {
    console.log(`\x1B[91m server error !!!!!!!!!!!!! \x1B[0m`, err, ctx);
})

app.listen(3000, () => {
    // for (let i = 0; i < 100; i++) {
    //     console.log(`\x1B[${i}m 颜色 \x1B[0m`, i);
    // }
    console.log("服务器启动完成:");
})

import "./routes/tags" 这个导入一定要有,需要在主页中导入对应的路由 访问:http://localhost:3000/tags 就可以直接访问到接口获取到对应数据。

参考