分享使用 Deno 搭建第一个 Web 应用
搭建 HTTP 服务
// server.ts
import { abc } from "https://deno.land/x/abc/mod.ts";
const app = abc();
app.start(":3000");
运行:
$ deno run -A server.ts
打开浏览器, 访问 http://127.0.0.1:3000
, 你会看到页面显示 {"statusCode":404,"error":"Not Found"}
.
加载静态资源
在 assets
目录下创建 index.html
, 在里面随便写点内容, 然后更改 server.ts
// server.ts
import { abc } from "https://deno.land/x/abc/mod.ts";
const app = abc();
app.static("/static", "assets");
app.start(":3000");
访问 http://127.0.0.1:3000/static/index.html
, 之前在 index.html
中键入的文件就会在页面中显示.
request & response
import { abc } from "https://deno.land/x/abc/mod.ts";
const app = abc();
const users = [
{
id: "1",
username: "Li Lei"
}
];
app
.static("/static", "assets")
.get("/user/", _ => users)
.get("/user/:id", c => {
const { id } = c.params;
return `<h3>Hello, ${users.find(u => u.id === id).username}</h3>`;
});
app.start(":3000");
访问 /user/
显示 [{"id":"1","username":"Li Lei"}]
.
访问 /user/1
显示 Hello, Li Lei
.
访问 /user/2
显示 {"statusCode":500,"error":"Internal Server Error","message":"Cannot read property 'username' of undefined"}
, 这是因为并没有 id 为 2 的用户.
使用装饰器
很多时候我们需要对请求的内容做一些简单的处理, 这里提供一种简单的数据绑定的方法:
// tsconfig.json
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}
import { abc, Binder } from "https://deno.land/x/abc/mod.ts";
const app = abc();
@Binder()
class UserDTO {
constructor(
public username: string,
public password: string,
public age: number
) {}
}
app
.post("/user", async c => {
const user = await c.bind(UserDTO);
return user;
})
.start(":3000");
运行:
$ deno run -A -c tsconfig.json server.ts
这时, 当接收到的数据中的数据类型与 DTO
中定义类型不同时, 会返回错误的信息, 而多余的数据则会被过滤掉.
异常
import { Status } from "https://deno.land/std/http/http_status.ts";
import { abc } from "https://deno.land/x/abc/mod.ts";
import { HttpException } from "https://deno.land/x/abc/http_exception.ts";
const app = abc();
app
.get("/admin", c => {
throw new HttpException("Forbidden", Status.Forbidden);
})
.start(":3000");
当访问 /admin
时, 则会返回 {"statusCode":403,"message":"Forbidden"}
.
http_exception.ts
中内置了很多异常可以直接使用:
- BadGatewayException
- BadRequestException
- ConflictException
- ForbiddenException
- GatewayTimeoutException
- GoneException
- TeapotException
- MethodNotAllowedException
- NotAcceptableException
- NotFoundException
- NotImplementedException
- RequestEntityTooLargeException
- RequestTimeoutException
- ServiceUnavailableException
- UnauthorizedException
- UnprocessableEntityException
- InternalServerErrorException
- UnsupportedMediaTypeException
中间件
中间件是一个围绕路由处理程序调用的函数. 中间件函数可以访问 request
和 response
对象.
让我们从实现一个简单的中间件功能开始.
function track(next: HandlerFunc) {
return function(c: Context) {
console.log(`request to ${c.path}`);
next(c);
};
}
中间件分为以下级别:
根级: 在路由处理请求之前执行根级中间件. 它可以通过
abc().pre()
注册.组级: 创建新组时, 你可以仅为该组注册中间件.
路由级: 定义新路由时, 你可以选择仅为其注册中间件.
有些情况下, 你希望基于某些条件跳过中间件, 对此每个中间件都有一个选项来定义函数 skipper(c:Context):boolean
.
abc().use(
logger({
skipper: c => {
return c.path.startsWith("/skipper");
}
})
);