1.1 使用oak框架开发用户服务

203 阅读2分钟

阶段说明

第一阶段,先不考虑具体业务,纯后端开发API。

目标:

  • 手写一个用户信息服务,包含完整的增删改查功能
  • 持久化数据,保障服务重启后数据不丢失
  • 异常处理
  • 参数校验
  • 学会使用oak_nest框架

oak是Deno的一个web服务框架,它是参考的Node.js的koa,二者高度相似,语法使用上没有太大差异。

想对二者以及Express、中间件有所了解的话,可以看看这篇《从koa到oak》。

hello world

新建一个文件src/main.ts,内容如下:

import { Application } from "https://deno.land/x/oak@v11.1.0/mod.ts";

const app = new Application();

app.use((ctx) => {
  ctx.response.body = "Hello World";
});

app.addEventListener("listen", ({ port }) => {
  console.log(`Listening on: "http://localhost:${port}"`);
});

await app.listen({ port: 8000 });

新建deno.jsonc文件:

{
  "version": "0.0.0",
  "name": "deno_blog",
  "tasks": {
    "dev": "deno run --allow-net --check --watch src/mod.ts",
  }
}

tasks相当于Node.js的script,也是为简化我们输入的命令而生的。你直接在命令行输入具体命令也是一样的。

Deno为解决抛弃了Package.json的后遗症,不得不找个文件来管理部分元数据,于是选择了deno.json或deno.jsonc文件。

  • --allow-net表示允许网络请求
  • --check表示运行时会校验TypeScript类型,默认不校验
  • --watch表示监听文件变化

在命令行中敲以下命令:deno task dev,可以看到控制台会印以下内容:

Task dev deno run --allow-net --check --watch src/main.ts
Watcher Process started.
Check file:///wk/deno/deno_blog/mod.ts
Listening on: "http://localhost:8000"

在浏览器输入http://localhost:8000,可以看到Hello World。

提交git

我们的代码使用git init初始化,并提交。

使用路由

为什么要使用路由?说到底是为了组装代码,所有的业务逻辑集中到一块,很难维护。所以现在不管是后端还是前端开发,基本都要使用路由。

我们先写个用户的增删改查,了解下它的用法。

还是刚才的mod.ts。

1. 定义一个User的接口

interface User {
  id: number;
  author: string;
  age: number;
}

2. 用Map维护用户信息

const users = new Map<number, User>();
const user1: User = {
  id: 1,
  author: "张三",
  age: 18,
};
users.set(user1.id, user1);

3. 把路由加上

import { Application, Router } from "https://deno.land/x/oak@v11.1.0/mod.ts";

const router = new Router();
router
  .get("/", (context) => {
    context.response.body = "hello world";
  })
  .get("/user", (context) => {
    context.response.body = Array.from(users.values());
  })
  .get("/user/:id", (context) => {
    const id = Number(context.params.id);
    if (users.has(id)) {
      context.response.body = users.get(id);
    } else {
      context.response.status = 404;
      context.response.body = "user not found";
    }
  })

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

app.addEventListener("listen", ({ port }) => {
  console.log(`Listening on: http://localhost:${port}`);
});

await app.listen({ port: 8000 });

在浏览器输入http://localhost:8000/,打印的还是hello world。

再输入http://localhost:8000/user/,这次就不一样了:

image.png

再输入http://localhost:8000/user/1,看到的只有张三的信息: image.png

而输入http://localhost:8000/user/2,看到的是user not found。这个标签页不要关闭。

4. 增加一个用户

在router.get()的最后再添加.post,也可以另起一行,直接router.post。

router.post("/user", async (context) => {
  const result = context.request.body({
    type: "json",
  });
  const value = await result.value;
  value.id = users.size + 1;
  users.set(value.id, value);
  context.response.body = value;
});

在刚才的浏览器标签页打开F12,在控制台敲以下代码,回车:

fetch("/user", {
  method: "post",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ author: "李四", age: 10 }),
});

image.png

之后,再刷新,是不是看到页面变了? image.png

这就完成了一个增加接口。你再打开http://localhost:8000/user/,看到的就是2个用户了: image.png

5. 修改用户信息

在RESTFul API中,修改操作是使用put。如果你不使用REST风格,使用post也是可以的。

router.put("/user/:id", async (context) => {
    const id = Number(context.params.id);
    if (!users.has(id)) {
      context.response.status = 404;
      context.response.body = "user not found";
      return;
    }
    const result = context.request.body({
      type: "json",
    });
    const value = await result.value;
    const user = users.get(id);
    user!.age = value.age;
    context.response.body = "update ok";
  });

注意:我们修改完代码后,因为我们启动命令用了--watch,Deno会自动重启服务,这时新增的用户已经丢失了。因为目前所有操作都是在内存中的,重启服务后内存就重置了,这就是为什么数据需要持久化。

仍是在浏览器的控制台,输入以下代码并执行:

fetch("/user/1", {
  method: "put",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ age: 5 }),
});

刷新页面,看到http://localhost:8000/user/的变化,用户张三的age已经变成5了。

image.png

6. 删除用户

router.delete("/user/:id", (context) => {
    const id = Number(context.params.id);
    if (users.has(id)) {
      users.delete(id);
      context.response.body = "delete ok";
    } else {
      context.response.status = 404;
      context.response.body = "user not found";
    }
  });

刷新页面,你看到的http://localhost:8000/user/仍是原来的数据。

在F12输入执行:

fetch("/user/1", {
  method: "delete",
  body: null,
});

再刷新页面,只剩下一个空数组了。

这样,一个用户列表的完整的增删改查过程就完成了。有兴趣的,可以不断重复上面的过程,增加新的用户、修改并删除。

作业

现在我们所有代码都写在src/main.ts里,它太臃肿了,你有什么好的主意吗?