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

前言

本次的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文件,指定需要编译的目录范围

json 复制代码
{
  "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文件

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,设置请求配置,挂载路由以及监听端口

ts 复制代码
/*
 * @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文件夹下新建几个文件夹,具体目录结构如下图:

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

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

ts 复制代码
// 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的返回结果进行一些处理

ts 复制代码
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文件

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文件

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文件

ts 复制代码
import router from "./mian";
const tagController = require('../controllers/tagsController');

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

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

ts 复制代码
import Router = require("koa-router");
import config from "../modules/Config";

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

export default router;

在index文件下进行路由挂载

ts 复制代码
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 就可以直接访问到接口获取到对应数据。

参考

全栈之路:node+ts+koa 开发环境搭建 - 掘金 (juejin.cn)

node + koa + ts 构建服务端应用 - 掘金 (juejin.cn)

相关推荐
guangzan1 小时前
DeepSeek-Lane:在 Cursor 内使用 DeepSeek V4 模型
typescript
网络点点滴4 小时前
简述Node.js运行时核心架构
架构·node.js
小粉粉hhh5 小时前
Node.js(三)——模块化
node.js
晓杰'5 小时前
从0到1实现 Balatro 游戏后端(1):项目规划与牌型判断实现
后端·websocket·typescript·node.js·游戏开发·项目实战·nestjs
@PHARAOH6 小时前
WHAT - npm和corepack
前端·npm·node.js
MPGWJPMTJT6 小时前
从 Volta 迁移到 mise:Windows 下 Node 版本管理切换记录
前端·node.js
zhangfeng11336 小时前
Remotion 渲染视频脚本 ,自动化编辑视频 Node.js 层面是“单线程 JS”,但在实际渲染时是“高度并行”的。
node.js·自动化·音视频
羽师7 小时前
Node.js和npx关系
node.js
Forget the Dream7 小时前
基于适配器模式的 Axios 封装实践
设计模式·typescript·axios·适配器模式
灵魂学者7 小时前
使用 Electron 打包项目构建 .EXE 桌面应用程序(简)
electron·node.js·vue·build·桌面应用程序