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

简介: Express是nodejs开发中普遍使用的一个框架,下面要谈的是如何结合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”。

UB4N@ODOU4[156_(E`ZJJ%3.png


“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 created
curl -d "username=joe&password=hard2guess" -X POST http://localhost:3000/login
# Success
curl -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 request
  if (request.body.something === 'foo') {
    // Failed criteria, send forbidden resposne
    return response.status(403).send('Forbidden');
  }
  // Succeeded, go to the next middleware
  next();
}


由此可知,一个中间件需要传入三个参数,分别是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强类型的支持,保证了程序的稳定性。



目录
相关文章
|
4天前
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实战指南
【9月更文挑战第6天】在数字化时代的潮流中,后端开发作为支撑现代Web和移动应用的核心,其重要性不言而喻。本文将深入浅出地介绍如何使用Node.js及其流行的框架Express来搭建一个高效、可扩展的后端服务。通过具体的代码示例和实践技巧,我们将探索如何利用这两个强大的工具提升开发效率和应用性能。无论你是后端开发的新手还是希望提高现有项目质量的老手,这篇文章都将为你提供有价值的见解和指导。
|
12天前
|
存储 JavaScript NoSQL
构建高效Web应用:使用Node.js和Express框架
【8月更文挑战第30天】本文将引导你了解如何使用Node.js和Express框架快速搭建一个高效的Web应用。通过实际的代码示例,我们将展示如何创建一个简单的API服务,并讨论如何利用中间件来增强应用功能。无论你是新手还是有经验的开发者,这篇文章都将为你提供有价值的见解。
|
11天前
|
大数据 数据处理 分布式计算
JSF 逆袭大数据江湖!看前端框架如何挑战数据处理极限?揭秘这场技术与勇气的较量!
【8月更文挑战第31天】在信息爆炸时代,大数据已成为企业和政府决策的关键。JavaServer Faces(JSF)作为标准的 Java Web 框架,如何与大数据技术结合,高效处理大规模数据集?本文探讨大数据的挑战与机遇,介绍 JSF 与 Hadoop、Apache Spark 等技术的融合,展示其实现高效数据存储和处理的潜力,并提供示例代码,助您构建强大的大数据系统。
21 0
|
11天前
|
JavaScript 安全 数据安全/隐私保护
深入Node.js与Express:从表单接收到数据验证的完整指南——实战技巧揭秘后端开发中的表单处理流程
【8月更文挑战第31天】在Web开发中,处理表单数据至关重要。本文通过实例详细讲解了如何利用Node.js与Express框架接收和验证表单数据。首先,搭建环境并初始化一个简单的Express应用;接着,演示如何接收用户注册表单中的`username`和`email`字段;最后,引入`joi`模块进行数据验证,确保数据安全准确。掌握这些技能有助于开发者构建更安全、友好的Web应用。随着Node.js和Express的不断进步,未来将有更多高级功能值得期待。
20 0
|
14天前
|
JavaScript 前端开发 中间件
构建高效后端服务:Node.js与Express框架的完美搭档
【8月更文挑战第28天】在追求高性能、可扩展和易维护的后端开发领域,Node.js和Express框架的组合提供了一种轻量级且灵活的解决方案。本文将深入探讨如何利用这一组合打造高效的后端服务,并通过实际代码示例展示其实现过程。
|
16天前
|
JavaScript 中间件 API
深入浅出Node.js后端框架——Express
【8月更文挑战第27天】在这篇文章中,我们将一起探索Node.js的热门框架Express。Express以其简洁、高效的特点,成为了许多Node.js开发者的首选框架。本文将通过实例引导你了解Express的核心概念和使用方法,让你快速上手构建自己的Web应用。
|
15天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的深度整合
【8月更文挑战第27天】 在现代Web开发中,后端服务的高效性至关重要。本文深入探讨了如何利用Node.js的非阻塞I/O特性和Express框架的简洁性来打造高性能的后端服务。我们将通过具体案例,展示如何在不牺牲代码可读性和可维护性的前提下,实现高效的请求处理和服务端逻辑。文章旨在为开发者提供一个清晰的指导,帮助他们在构建后端服务时做出更明智的技术选择。
|
1月前
|
存储 缓存 JavaScript
构建高效后端服务:Node.js与Express框架的实战应用
【8月更文挑战第2天】在数字化时代的浪潮中,后端服务的构建成为了软件开发的核心。本文将深入探讨如何利用Node.js和Express框架搭建一个高效、可扩展的后端服务。我们将通过实际代码示例,展示从零开始创建一个RESTful API的全过程,包括路由设置、中间件使用以及数据库连接等关键步骤。此外,文章还将触及性能优化和安全性考量,旨在为读者提供一套完整的后端开发解决方案。让我们一同走进Node.js和Express的世界,探索它们如何助力现代Web应用的开发。
|
14天前
|
JSON JavaScript 中间件
深入浅出Node.js后端开发之Express框架应用
【8月更文挑战第29天】本文将带领读者快速了解并掌握使用Express框架进行Node.js后端开发的基础和进阶知识。我们将一起探索Express的安装、基本使用方法,并通过实际代码示例学习如何搭建一个简单的Web服务器。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
15天前
|
JavaScript PHP 开发者
PHP中的异常处理与自定义错误处理器构建高效Web应用:Node.js与Express框架实战指南
【8月更文挑战第27天】在PHP编程世界中,异常处理和错误管理是代码健壮性的关键。本文将深入探讨PHP的异常处理机制,并指导你如何创建自定义错误处理器,以便优雅地管理运行时错误。我们将一起学习如何使用try-catch块捕获异常,以及如何通过set_error_handler函数定制错误响应。准备好让你的代码变得更加可靠,同时提供更友好的错误信息给最终用户。