【详细教程】教你如何使用Node + Express + Typescript开发一个应用

Express是nodejs开发中普遍使用的一个框架,下面要谈的是如何结合Typescript去使用。

目标

我们的目标是能够使用Typescript快速开发我们的应用程序,而最终我们的应用程序却是编译为原始的JavaScript代码,以由nodejs运行时来执行。

初始化设置

首要的是我们要创建一个目录名为express-typescript-app来存放我们的项目代码:

mkdir express-typescript-app
cd express-typescript-app

为了实现我们的目标,首先我们需要区分哪些是线上程序依赖项,哪些是开发依赖项,这样可以确保最终编译的代码都是有用的。

在这个教程中,将使用yarn命令作为程序包管理器,当然npm也是一样可以的。

生产环境依赖

express作为程序的主体框架,在生产环境中是必不可少的,需要安装

yarn add express

这样当前目录下就生成了一个package.json 文件,里面暂时只有一个依赖

开发环境依赖项

在开发环境中我们将要使用Typescript编写代码。所以我们需要安装typescript。另外也需要安装node和express的类型声明。安装的时候带上- D参数来确保它是开发依赖。

yarn add -D typescript @types/express @types/node

安装好之后,还有一点值得注意,我们并不想每次代码更改之后还需要手动去执行编译才生效。这样体验太不好了!所以我们需要额外添加几个依赖:

  • ts-node: 这个安装包是为了不用编译直接运行typescript代码,这个对本地开发太有必要了

  • nodemon:这个安装包在程序代码变更之后自动监听然后重启开发服务。搭配ts-node模块就可以做到编写代码及时生效。

因此这两个依赖都是在开发的时候需要的,而不需编译进生产环境的。

yarn add -D ts-node nodemon

配置我们的程序运行起来

配置Typescript文件

为我们将要用的typescript设置配置文件,创建tsconfig.json文件

touch tsconfig.json

现在让我们给配置文件添加编译相关的配置参数:

  • module: "commonjs" — 如果使用过node的都知道,这个作为编译代码时将被编译到最终代码是必不可少的。

  • esModuleInterop: true — 这个选项允许我们默认导出的时候使用*代替导出的内容。

  • target: "es6" — 不同于前端代码,我们需要控制运行环境,得确保使用的node版本能正确识别ES6语法。

  • rootDir: "./" — 设置代码的根目录为当前目录。

  • outDir: "./build" — 最终将Typescript代码编译成执行的Javascript代码目录。

  • strict: true — 允许严格类型检查。

最终tsconfig.json文件内容如下:

{"compilerOptions": {"module": "commonjs","esModuleInterop": true,"target": "es6","rootDir": "./","outDir": "./build","strict": true}
}

配置package.json脚本

目前还没有 package.json文件的scripts项,我们需要添加几个脚本:第一个是start启动开发模式,另一个是 build打包线上环境代码的命令。

启动开发模式我们需要执行nodemon index.ts,而打包生产代码,我们已经在tsconfig.json中给出了所有需要的信息,所以我们只需要执行tsc命令。

此刻下面是你package.json文件中所有的内容,也可能由于我们创建项目的时间不一样,导致依赖的版本号不一样。

{"dependencies": {"express": "^4.17.1"},"devDependencies": {"@types/express": "^4.17.11","@types/node": "^14.14.22","nodemon": "^2.0.7","ts-node": "^9.1.1","typescript": "^4.1.3"}
}

Git配置

如果使用git来管理代码,还需要添加.gitignore文件来忽视node_modules目录和build目录

touch .gitignore

添加忽视的内容

node_modules
build

至此,所有的安装过程已经结束,比单纯的无Typescript版本可能稍微复杂点。

创建我们的Express应用

让我们来正式开始创建express应用。首先创建主文件index.ts

touch index.ts

然后添加案例代码,在网页中输出“hello world”

import express from 'express';const app = express();
const PORT = 3000;app.get('/', (req, res) => {res.send('Hello world');
});app.listen(PORT, () => {console.log(`Express with Typescript! http://localhost:${PORT}`);
});

在终端命令行执行启动命令 yarn run start

yarn run start

接下来会输出以下内容:

[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node index.ts`
Express with Typescript! http://localhost:3000

我们可以看到nodemon模块已经监听到所有文件的变更后使用ts-node index.ts命令启动了我们的应用。我们现在可以在浏览器打开网址http://localhost:3000,将会看到网页中输出我们想要的“hello world”。

“Hello World”以外的功能

我们的 “Hello World”应用算是创建好了,但是我们不仅于此,还要添加一些稍微复杂点的功能,来丰富一下应用。大致功能包括:

  • 保存一系列的用户名和与之匹配的密码在内存中

  • 允许提交一个POST请求去创建一个新的用户

  • 允许提交一个POST请求让用户登录,并且接受因为错误认证返回的信息

让我们一个个去实现以上功能!

保存用户

首先,我们创建一个types.ts文件来定义我们用到的User类型。后面所有类型定义都写在这个文件中。

touch types.ts

然后导出定义的User类型

export type User = { username: string; password: string };

好了。我们将使用内存来保存所有的用户,而不是数据库或者其它方式。根目录下创建一个data目录,然后在里面新建users.ts文件

mkdir data
touch data/users.ts

现在在users.ts文件里创建一个User类型的空数组

import { User } from "../types";const users: User[] = [];

提交新用户

接下来我们希望向应用提交一个新用户。我们在这里将要用到处理请求参数的中间件body-parse

yarn add body-parser

然后在主文件里导入并使用它

import express from 'express';
import bodyParser from 'body-parser';const app = express();
const PORT = 3000;app.use(bodyParser.urlencoded({ extended: false }));app.get('/', (req, res) => {res.send('Hello world');
});app.listen(PORT, () => {console.log(`Express with Typescript! http://localhost:${PORT}`);
});

最后,我们可以在users文件里创建POST请求处理程序。 该处理程序将执行以下操作:

  • 校验请求体中是否包含了用户名和密码,并且进行有效性验证

  • 一旦提交的用户名密码不正确返回状态码为400的错误信息

  • 添加一个新用户到users数组中

  • 返回一个201状态的错误信息

让我们开始,首先,在data/users.ts文件中创建一个addUser的方法

import { User } from '../types';const users: User[] = [];const addUser = (newUser: User) => {users.push(newUser);
};

然后回到index.ts文件中添加一条"/users"的路由

import express from 'express';
import bodyParser from 'body-parser';
import { addUser } from './data/users';const app = express();
const PORT = 3000;app.use(bodyParser.urlencoded({ extended: false }));app.get('/', (req, res) => {res.send('Hello world');
});app.post('/users', (req, res) => {const { username, password } = req.body;if (!username?.trim() || !password?.trim()) {return res.status(400).send('Bad username or password');}addUser({ username, password });res.status(201).send('User created');
});app.listen(PORT, () => {console.log(`Express with Typescript! http://localhost:${PORT}`);
});

这里的逻辑不复杂,我们简单解释一下,首先请求体中要包含usernamepassword两个变量,而且使用trim()函数去除收尾的空字符,保证它的长度大于0。如果不满足,返回400状态和自定义错误信息。如果验证通过,则将用户信息添加到users数组并且返回201状态回来。

注意:你有没有发现users数组是没有办法知道有没有同一个用户被添加两次的,我们暂且不考虑这种情况。

让我们重新打开一个终端(不要关掉运行程序的终端),在终端里通过curl命令来发出一个POST请求注册接口

curl -d "username=foo&password=bar" -X POST http://localhost:3000/users

你将会在终端的命令行中发现输出了下面的信息

User created

然后再请求一次接口,这次password仅仅为空字符串,测试一下请求失败的情况

curl -d "username=foo&password= " -X POST http://localhost:3000/users

没有让我们失望,成功返回了一下错误信息

Bad username or password

登录功能

登录有点类似,我们从请求体中拿到usernamepassword的值然后通过Array.find方法去users数组中查找相同的用户名和密码组合,返回200状态码说明用户登录成功,而401状态码表示用户不被授权,登录失败。

首先我们在data/users.ts文件中添加getUser方法:

import { User } from '../types';const users: User[] = [];export const addUser = (newUser: User) => {users.push(newUser);
};export const getUser = (user: User) => {return users.find((u) => u.username === user.username && u.password === user.password);
};

这里getUser方法将会从users数组里返回与之匹配用户或者undefined

接下来我们将在index.ts里调用getUser方法

import express from 'express';
import bodyParser from 'body-parser';
import { addUser, getUser } from "./data/users';const app = express();
const PORT = 3000;app.use(bodyParser.urlencoded({ extended: false }));app.get('/', (req, res) => {res.send('Hello word');
});app.post('/users', (req, res) => {const { username, password } = req.body;if (!username?.trim() || !password?.trim()) {return res.status(400).send('Bad username or password');}addUser({ username, password });res.status(201).send('User created');
});app.post('/login', (req, res) => {const { username, password } = req.body;const found = getUser({username, password})if (!found) {return res.status(401).send('Login failed');}res.status(200).send('Success');
});app.listen(PORT, () => {console.log(`Express with Typescript! http://localhost:${PORT}`);
});

现在我们还是用curl命令去请求注册接口和登录接口,登录接口请求两次,一次成功一次失败

curl -d "username=joe&password=hard2guess" -X POST http://localhost:3000/users
# User createdcurl -d "username=joe&password=hard2guess" -X POST http://localhost:3000/login
# Successcurl -d "username=joe&password=wrong" -X POST http://localhost:3000/login
# Login failed

没问题,结果都按我们预想的顺利返回了

探索Express类型

您可能已经发现,讲到现在,好像都是一些基础的东西,Express里面比较深的概念没有涉及到,比如自定义路由,中间件和句柄等功能,我们现在就来重构它。

自定义路由类型

或许我们希望的是创建这样一个标准的路由结构像下面这样

const route = {method: 'post',path: '/users',middleware: [middleware1, middleware2],handler: userSignup,
};

我们需要在types.ts文件中定义一个Route类型。同时也需要从Express库中导出相关的类型:RequestResponseNextFunctionRequest表示客户端的请求数据类型,Response是从服务器返回值类型,NextFunction则是next()方法的签名,如果使用过express的中间件应该很熟悉。

types.ts文件中,重新定义Route类型

export type User = { username: string; password: string };type Method =| 'get'| 'head'| 'post'| 'put'| 'delete'| 'connect'| 'options'| 'trace'| 'patch';export type Route = {method: Method;path: string;middleware: any[];handler: any;
};

如果你熟悉express中间件的话,你应该知道一个典型的中间件长这样:

function middleware(request, response, next) {// Do some logic with the requestif (request.body.something === 'foo') {// Failed criteria, send forbidden resposnereturn response.status(403).send('Forbidden');}// Succeeded, go to the next middlewarenext();
}

由此可知,一个中间件需要传入三个参数,分别是RequestResponseNextFunction类型。因此如果需要我们创建一个Middleware类型:

import { Request, Response, NextFunction } from 'express';type Middleware = (req: Request, res: Response, next: NextFunction) => any;

然后express已经有了一个叫RequestHandler类型,所以在这里我们只需要从express导出就好了,如果取个别名可以采用类型断言。

import { RequestHandler as Middleware } from 'express';export type User = { username: string; password: string };type Method =| 'get'| 'head'| 'post'| 'put'| 'delete'| 'connect'| 'options'| 'trace'| 'patch';export type Route = {method: Method;path: string;middleware: Middleware[];handler: any;
};

最后我们只需要为handler指定类型。这里的handler应该是程序执行的最后一步,因此我们在设计的时候就不需要传入next参数了,类型也就是RequestHandler去掉第三个参数。

import { Request, Response, RequestHandler as Middleware } from 'express';export type User = { username: string; password: string };type Method =| 'get'| 'head'| 'post'| 'put'| 'delete'| 'connect'| 'options'| 'trace'| 'patch';export type Handler = (req: Request, res: Response) => any;export type Route = {method: Method;path: string;middleware: Middleware[];handler: Handler;
};

添加一些项目结构

我们需要通过增加一些结构来把中间件和处理程序从index.ts文件中移除

创建处理器

我们把一些处理方法移到handlers目录中

mkdir handlers
touch handlers/user.ts

那么在handlers/user.ts文件中,我们添加如下代码。和用户注册相关的处理代码已经被我们从index.ts文件中重构到这里。重要的是我们可以确定signup方法满足我们定义的Handlers类型

import { addUser } from '../data/users';
import { Handler } from '../types';export const signup: Handler = (req, res) => {const { username, password } = req.body;if (!username?.trim() || !password?.trim()) {return res.status(400).send('Bad username or password');}addUser({ username, password });res.status(201).send('User created');
};

同样,我们把创建auth处理器添加login方法

touch handlers/auth.ts

添加以下代码

import { getUser } from '../data/users';
import { Handler } from '../types';export const login: Handler = (req, res) => {const { username, password } = req.body;const found = getUser({ username, password });if (!found) {return res.status(401).send('Login failed');}res.status(200).send('Success');
};

最后也给我们的首页增加一个处理器

touch handlers/home.ts

功能很简单,只要输出文本

import { Handler } from '../types';export const home: Handler = (req, res) => {res.send('Hello world');
};

中间件

现在还没有任何的自定义中间件,首先创建一个middleware目录

mkdir middleware

我们将添加一个打印客户端请求路径的中间件,取名requestLogger.ts

touch middleware/requestLogger.ts

从express库中导出需要定义的中间件类型的RequestHandler类型

import { RequestHandler as Middleware } from 'express';export const requestLogger: Middleware = (req, res, next) => {console.log(req.path);next();
};

创建路由

既然我们已经定义了一个新的Route类型和自己的一些处理器,就可以把路由定义独立出来一个文件,在根目录创建routes.ts

touch routes.ts

以下是该文件的所有代码,为了演示就只给/login添加了requestLogger中间件

import { login } from './handlers/auth';
import { home } from './handlers/home';
import { signup } from './handlers/user';
import { requestLogger } from './middleware/requestLogger';
import { Route } from './types';export const routes: Route[] = [{method: 'get',path: '/',middleware: [],handler: home,},{method: 'post',path: '/users',middleware: [],handler: signup,},{method: 'post',path: '/login',middleware: [requestLogger],handler: login,},
];

重构index.ts文件

最后也是最重要的一步就是简化index.ts文件。我们通过一个forEach循环routes文件中声明的路由信息来代替所有的route相关的代码。这样做最大的好处是为所有的路由定义了类型。

import express from 'express';
import bodyParser from 'body-parser';
import { routes } from './routes';const app = express();
const PORT = 3000;app.use(bodyParser.urlencoded({ extended: false }));routes.forEach((route) => {const { method, path, middleware, handler } = route;app[method](path, ...middleware, handler);
});app.listen(PORT, () => {console.log(`Express with Typescript! http://localhost:${PORT}`);
});

这样看起来代码结构清晰多了,架构的好处就是如此。另外有了Typescript强类型的支持,保证了程序的稳定性。

完整代码

Github:
https://github.com/fantingsheng/express-typescript-app

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/354618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

结构型模式 适配器模式

结构型模式 适配器模式 适用于: 是将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 /*** 结构型模式 适配器模式* Adapter模式也叫适配器模式,是构造型模式之一,通过Adapter模式可…

乐哥学AI_Python(二):Numpy索引,切片,常用函数

Numpy的索引和切片 ndarray对象的内容可以通过索引和切片查看和修改。 索引:ndarray对象中的元素索引基于0开始 切片:对数组里某个片段区域的描述 数组的切片也可以理解为原始数组的局部视图,都是指向内存中的原始数组,所以不同于…

仅使用HTML和CSS实现的标签云效果

标签云的效果在博客和网站上不难见到,它其实就是带有超链接的某些关键字,为了达到强调主题的作用。通常出现概率比较大或者受欢迎的标签文字显示比较大,相反的就显示的小。来源于TagCrowd.com我们就不去深入研究标签云带来的效率上的提升和可…

捍卫Java

因此,我们不时发布了一本电子书,名为“十大Java性能问题” 。 毫无例外,一些人回答说“问题是您正在使用Java”。 显然,Java一直在受到批评,人们已经预测了它的消亡已有一段时间了。 当然,它不像Python&am…

vuex的使用二

1.先看项目的目录结构 2.在main.js里需要引入store这个文件并挂在实例上 import store from ./store/store ............new Vue({el: #app,router,store,template: <App/>,components: { App } }) 3.store.js里引入action.js和mutation.js文件 // 状态管理器 import Vue…

收到短信验证码自动填充到表单,竟然是这么玩的

苹果系统上的App和网站可以实现来自短信的验证码自动填充表单的功能&#xff0c;通常你是怎么实现这个功能的&#xff1f;有一种实现方式可能你不知道&#xff0c;单纯的HTML标签就能实现&#xff0c;不需要任何的Javascript代码该特性第一次发布是在 WWDC 2018[1]&#xff1a;…

web实现数据交互的几种常见方式

前言在当今社会&#xff0c;作为一名前端程序猿&#xff0c;并不是一昧的去制作静态页面就可以满足滴&#xff1b;你说你会制作网页&#xff0c;好吧&#xff0c;只能说你算是一个前端程序猿。但这是你作为一个程序猿最基本的能力&#xff0c;并不会为你进行加分&#xff1b; 我…

【超详细教程】如何使用TypeScript和GraphQL开发应用

GraphQL是一个专为构建灵活的API而生的强大的查询语言。它允许您为数据定义类型系统&#xff0c;因此在执行查询时&#xff0c;它仅返回所需的数据。与TypeScript一起使用时&#xff0c;GraphQL可以为开发人员提供更好的体验&#xff0c;因为它们都是类型语言。TypeScript是Jav…

jpa jsf_完整Web应用程序Tomcat JSF Primefaces JPA Hibernate –第2部分

jpa jsf托管豆 这篇文章是本教程第1部分的继续。 在“ com.mb”包中&#xff0c;您将需要创建以下类&#xff1a; package com.mb;import org.primefaces.context.RequestContext;import com.util.JSFMessageUtil;public class AbstractMB {private static final String KEEP…

【利好工具】JavaScript及时运行调试工具

今天要介绍的这个工具叫RunJS[1]&#xff0c;是运行在各大系统上的客户端&#xff0c;目前支持主流的macOS、Windows、Debian和Universal Linux平台。你可以在上面编写你的JavaScript或TypeScript代码&#xff0c;并且获得即时反馈。它的左边是代码区域&#xff0c;右边是输出结…

CIFAR-10 dataset 的下载与使用

基本信息 CIFAR-10 是一个包含60000张图片的数据集。其中每张照片为32*32的彩色照片&#xff0c;每个像素点包括RGB三个数值&#xff0c;数值范围 0 ~ 255。 所有照片分属10个不同的类别&#xff0c;分别是 airplane, automobile, bird, cat, deer, dog, frog, horse, ship, tr…

教你如何创建一款属于自己的VSCode主题

你有没有想过创建一款属于自己的VSCode主题&#xff1f;没有你想像的那么难&#xff0c;但是真正实现起来也不是那么轻松&#xff0c;需要你对将要去改变的主题的一些属性要有所了解和准备。如果你要想让你的主题适用于多种编程语言&#xff0c;并且看起来还很漂亮的话&#xf…

调试

http://www.cnblogs.com/CARPE-DIEM-wu/p/7798119.html 黄金规则 从帮助台得到的观点是不明确的 检查插头 不要想,而要看 回到目录制造失败 回到目录不要想,而要看 回到目录分而治之 回到目录一次只改一个地方 回到目录保持审计跟踪 回到目录检查插头 回到目录获得全新观点 回到…

【视频内含福利】原来手机套壳视频是这么做出来的

最近视频圈子里都在发这种视频&#xff0c;究竟是怎么做出来的&#xff1f;研究下来发现原来是用的这款名字叫Record Maker的APP&#xff0c;旨在帮助用户轻松快速给视频加上手机壳的效果。目前App Store上的版本是1.2.1&#xff0c;最近一次更新支持了iPhone11/iPhone12模型&a…

apache thrift_使用Java快速入门的Apache Thrift

apache thriftApache Thrift是由facebook创建的RPC框架&#xff0c;现在它是一个Apache项目。 Thrift允许您在不依赖语言的定义文件中定义数据类型和服务接口。 该定义文件用作编译器的输入&#xff0c;以生成用于构建通过不同编程语言进行通信的RPC客户端和服务器的代码。 您也…

前端八大灵感设计,代码变为现实

我们都知道掌握编程技术是一项很难的事情&#xff0c;没有捷径可走。需要我们在日复一日的工作中去锻炼。如果想要达到擅长甚至需要长年累月的积累。下面列举了一些可以给你带来编码灵感的例子&#xff0c;相信你看到会有想去写代码的冲动。因为一个好的工程师&#xff0c;是不…

Apple 公司开发者账号添加团队成员

Apple 公司开发者账号添加团队成员 简介 首先公司内部团队开发需要的账号类型为公司账号&#xff0c;可以添加团队成员协同开发。 账号下的团队成员有三种角色&#xff1a; Team Agent (代理) 代理: 就是注册开发者账号的那一个&#xff0c;权限最高&#xff0c;续费和创建开发…

八个使前端工程师惊艳的效果设计,码否?

我们都知道掌握编程技术是一项很难的事情&#xff0c;没有捷径可走。需要我们在日复一日的工作中去锻炼。如果想要达到擅长甚至需要长年累月的积累。 下面列举了一些可以给你带来编码灵感的例子&#xff0c;相信你看到会有想去写代码的冲动。 因为一个好的工程师&#xff0c;…

OpenStack_I版 1.准备过程

openstack是一个开源云平台&#xff0c;python开发此次部署为实验环境&#xff0c; 采用扁平化简单的网络架构部署  优点&#xff1a;低耦合的&#xff0c;模块化Nova 计算资源池Glance 镜像服务Swift 对象存储Horizon 对计算资源&#xff0c;网络资源起别…

快点,再快点!

现在看视频不来个两倍速&#xff08;或者更快&#xff09;都觉得在浪费生命。特别是在看视频教程的时候&#xff0c;文字我们可以做到一目十行&#xff0c;但是视频呢&#xff0c;如果有字幕&#xff0c;我们甚至不用听清&#xff0c;用3倍速或者4倍速完全没有问题&#xff0c;…