900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 【详细教程】教你如何使用Node + Express + Typescript开发一个应用

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

时间:2018-06-23 21:12:35

相关推荐

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

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

目标

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

初始化设置

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

mkdirexpress-typescript-appcdexpress-typescript-app

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

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

生产环境依赖

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

yarnaddexpress

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

开发环境依赖项

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

yarnadd-Dtypescript@types/express@types/node

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

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

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

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

yarnadd-Dts-nodenodemon

配置我们的程序运行起来

配置Typescript文件

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

touchtsconfig.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_modulesbuild

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

创建我们的Express应用

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

touchindex.ts

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

importexpressfrom'express';constapp=express();constPORT=3000;app.get('/',(req,res)=>{res.send('Helloworld');});app.listen(PORT,()=>{console.log(`ExpresswithTypescript!http://localhost:${PORT}`);});

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

yarnrunstart

接下来会输出以下内容:

[nodemon]2.0.7[nodemon]torestartatanytime,enter`rs`[nodemon]watchingpath(s):*.*[nodemon]watchingextensions:ts,json[nodemon]starting`ts-nodeindex.ts`ExpresswithTypescript!http://localhost:3000

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

“Hello World”以外的功能

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

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

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

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

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

保存用户

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

touchtypes.ts

然后导出定义的User类型

exporttypeUser={username:string;password:string};

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

mkdirdatatouchdata/users.ts

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

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

提交新用户

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

yarnaddbody-parser

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

importexpressfrom'express';importbodyParserfrom'body-parser';constapp=express();constPORT=3000;app.use(bodyParser.urlencoded({extended:false}));app.get('/',(req,res)=>{res.send('Helloworld');});app.listen(PORT,()=>{console.log(`ExpresswithTypescript!http://localhost:${PORT}`);});

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

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

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

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

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

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

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

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

importexpressfrom'express';importbodyParserfrom'body-parser';import{addUser}from'./data/users';constapp=express();constPORT=3000;app.use(bodyParser.urlencoded({extended:false}));app.get('/',(req,res)=>{res.send('Helloworld');});app.post('/users',(req,res)=>{const{username,password}=req.body;if(!username?.trim()||!password?.trim()){returnres.status(400).send('Badusernameorpassword');}addUser({username,password});res.status(201).send('Usercreated');});app.listen(PORT,()=>{console.log(`ExpresswithTypescript!http://localhost:${PORT}`);});

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

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

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

curl-d"username=foo&password=bar"-XPOSThttp://localhost:3000/users

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

Usercreated

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

curl-d"username=foo&password="-XPOSThttp://localhost:3000/users

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

Badusernameorpassword

登录功能

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

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

import{User}from'../types';constusers:User[]=[];exportconstaddUser=(newUser:User)=>{users.push(newUser);};exportconstgetUser=(user:User)=>{returnusers.find((u)=>u.username===user.username&&u.password===user.password);};

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

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

importexpressfrom'express';importbodyParserfrom'body-parser';import{addUser,getUser}from"./data/users';constapp=express();constPORT=3000;app.use(bodyParser.urlencoded({extended:false}));app.get('/',(req,res)=>{res.send('Helloword');});app.post('/users',(req,res)=>{const{username,password}=req.body;if(!username?.trim()||!password?.trim()){returnres.status(400).send('Badusernameorpassword');}addUser({username,password});res.status(201).send('Usercreated');});app.post('/login',(req,res)=>{const{username,password}=req.body;constfound=getUser({username,password})if(!found){returnres.status(401).send('Loginfailed');}res.status(200).send('Success');});app.listen(PORT,()=>{console.log(`ExpresswithTypescript!http://localhost:${PORT}`);});

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

curl-d"username=joe&password=hard2guess"-XPOSThttp://localhost:3000/users#Usercreatedcurl-d"username=joe&password=hard2guess"-XPOSThttp://localhost:3000/login#Successcurl-d"username=joe&password=wrong"-XPOSThttp://localhost:3000/login#Loginfailed

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

探索Express类型

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

自定义路由类型

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

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

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

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

exporttypeUser={username:string;password:string};typeMethod=|'get'|'head'|'post'|'put'|'delete'|'connect'|'options'|'trace'|'patch';exporttypeRoute={method:Method;path:string;middleware:any[];handler:any;};

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

functionmiddleware(request,response,next){//Dosomelogicwiththerequestif(request.body.something==='foo'){//Failedcriteria,sendforbiddenresposnereturnresponse.status(403).send('Forbidden');}//Succeeded,gotothenextmiddlewarenext();}

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

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

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

import{RequestHandlerasMiddleware}from'express';exporttypeUser={username:string;password:string};typeMethod=|'get'|'head'|'post'|'put'|'delete'|'connect'|'options'|'trace'|'patch';exporttypeRoute={method:Method;path:string;middleware:Middleware[];handler:any;};

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

import{Request,Response,RequestHandlerasMiddleware}from'express';exporttypeUser={username:string;password:string};typeMethod=|'get'|'head'|'post'|'put'|'delete'|'connect'|'options'|'trace'|'patch';exporttypeHandler=(req:Request,res:Response)=>any;exporttypeRoute={method:Method;path:string;middleware:Middleware[];handler:Handler;};

添加一些项目结构

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

创建处理器

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

mkdirhandlerstouchhandlers/user.ts

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

import{addUser}from'../data/users';import{Handler}from'../types';exportconstsignup:Handler=(req,res)=>{const{username,password}=req.body;if(!username?.trim()||!password?.trim()){returnres.status(400).send('Badusernameorpassword');}addUser({username,password});res.status(201).send('Usercreated');};

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

touchhandlers/auth.ts

添加以下代码

import{getUser}from'../data/users';import{Handler}from'../types';exportconstlogin:Handler=(req,res)=>{const{username,password}=req.body;constfound=getUser({username,password});if(!found){returnres.status(401).send('Loginfailed');}res.status(200).send('Success');};

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

touchhandlers/home.ts

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

import{Handler}from'../types';exportconsthome:Handler=(req,res)=>{res.send('Helloworld');};

中间件

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

mkdirmiddleware

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

touchmiddleware/requestLogger.ts

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

import{RequestHandlerasMiddleware}from'express';exportconstrequestLogger:Middleware=(req,res,next)=>{console.log(req.path);next();};

创建路由

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

touchroutes.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';exportconstroutes: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相关的代码。这样做最大的好处是为所有的路由定义了类型。

importexpressfrom'express';importbodyParserfrom'body-parser';import{routes}from'./routes';constapp=express();constPORT=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(`ExpresswithTypescript!http://localhost:${PORT}`);});

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

完整代码

Github:

/fantingsheng/express-typescript-app

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。