【譯】Deno + MongoDB 構建 CRUD API

原文地址:Create a server with deno and mongo[1]node

原文做者:Kailas Walldoddigit

譯者:Tony.Xugithub

讀完這篇文章,你應該能夠掌握:web

  • 使用 deno_mongo 操做 mongodb 數據庫
  • 構建 CRUD API 來管理員工信息
  • 構建 API 控制器( Controller
  • 使用簡易 deno 框架 abc
  • 使用 denv 建立環境變量

準備工做

首先,安裝 deno。能夠查閱這篇文檔[2],根據當前系統來選擇合適的命令行進行安裝。mongodb

PS: 截止做者(Kailas Walldoddi)寫這篇文章時,deno 版本是 1.0.0typescript

PSS:截止譯者(Tony.Xu)寫這篇譯文和運行樣例時,deno 版本是 1.1.1deno_mongo 版本是 0.8.0,推薦參考掘金上韓亦樂大佬這篇文章進行安裝:Deno 鑽研之術:(1) Hello,從多樣化安裝到簡單實戰[3]數據庫

友情提示:目前 Mac 用戶使用 homebrew 安裝版本只有 0.4.2,運行本文章代碼會報錯express

本文最後附 Mac 安裝 1.1.1 版本 deno 的命令json

正式開始

爲了實現咱們爲服務器設計的功能,咱們須要一個框架(相似 Node.js 中的 express)。在這裏,咱們使用 abc 這個簡易的 deno 框架來建立 web 應用(除了 abc,你還有不少其餘選擇,好比:alosaurespressofenoak…… )api

首先,咱們在項目根目錄下新建 .env 文件,用來聲明環境變量。

DB_NAME=deno_demo
DB_HOST_URL=mongodb://localhost:27017 複製代碼

接着,構建異常處理中間件來處理控制器中捕獲的報錯。utils/middleware.ts

// utils/middleware.ts
 import { MiddlewareFunc } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts"; export class ErrorHandler extends Error {  status: number;  constructor(message: string, status: number) {  super(message);  this.status = status;  } } export const ErrorMiddleware: MiddlewareFunc = (next: any) =>  async (c: any) => {  try {  await next(c);  } catch (err) {  const error = err as ErrorHandler;  c.response.status = error.status || 500;  c.response.body = error.message;  }  };  複製代碼

而後,編寫服務器主程序。server.ts

// server.ts
 // 經過 url 直接引入遠程模塊。首次運行後,deno 會下載並緩存該模塊 import { Application } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts"; // 使用 denv 來加載 .env 中配置的環境變量 import "https://deno.land/x/denv/mod.ts"; // 餘下代碼和 `express` 幾乎同樣,沒什麼特別的。 import {  fetchAllEmployees,  createEmployee,  fetchOneEmployee,  updateEmployee,  deleteEmployee, } from "./controllers/employees.ts"; import { ErrorMiddleware } from "./utils/middlewares.ts";  const app = new Application();  app.use(ErrorMiddleware);  app.get("/employees", fetchAllEmployees)  .post("/employees", createEmployee)  .get("/employees/:id", fetchOneEmployee)  .put("/employees/:id", updateEmployee)  .delete("/employees/:id", deleteEmployee)  .start({ port: 5000 });  console.log(`server listening on http://localhost:5000`); 複製代碼

代碼第一行,經過 url 直接引入遠程模塊。首次運行後,deno 會下載並緩存該模塊。

代碼第二行,使用 denv 來加載 .env 中配置的環境變量。

餘下代碼和 express 幾乎同樣,沒什麼特別的。

接下來咱們要爲服務器配置 mongodb 鏈接。幸運的是,已經有現成的 deno 版本的 MongoDB 驅動:deno_mongo。雖然目前 deno_mongo 仍在開發中,而且還未涵蓋 mongodb 驅動的所有方法,不過用來作一個小 demo 仍是 OK 的。

config/ 目錄下新建 db.ts 文件

// /config/db.ts
// 注:原文版本 deno_mongo 0.6.0,與 deno 1.1.1 不兼容,須要升級到 0.8.0 import { init, MongoClient } from "https://deno.land/x/mongo@v0.8.0/mod.ts";  // 注:原文建立 db 實例的操做有點複雜…… 如下參照了 deno_mongo 官方文檔 // https://github.com/manyuanrong/deno_mongo/tree/master  // db 名稱 const dbName = Deno.env.get("DB_NAME") || "deno_demo"; // db url const dbHostUrl = Deno.env.get("DB_HOST_URL") || "mongodb://localhost:27017"; // 建立鏈接 const client = new MongoClient(); // 創建鏈接 client.connectWithUri(dbHostUrl); const db = client.database(dbName); export default db; 複製代碼

接下來,編寫控制器,先重新建員工 createEmployee 開始

// /controllers/employee.ts
 import { HandlerFunc, Context } from "https://deno.land/x/abc@v1.0.0-rc2/mod.ts"; import db from '../config/db.ts'; import { ErrorHandler } from "../utils/middlewares.ts";  const employees = db.collection('employees');  // 定義 schema interface Employee {  _id: {  $oid: string;  };  name: string;  age: number;  salary: number; }  /**  * 新增員工  * @param c Context  * @returns $oid 目前 deno_mongo 新增時只返回 _id  */ export const createEmployee: HandlerFunc = async (c: Context) => {  try {  if (c.request.headers.get("content-type") !== "application/json") {  throw new ErrorHandler("Invalid body", 422);  }  const body = await (c.body());  if (!Object.keys(body).length) {  throw new ErrorHandler("Request body can not be empty!", 400);  }  const { name, salary, age } = body;   const insertedEmployee = await employees.insertOne({  name,  age,  salary,  });   return c.json(insertedEmployee, 201);  } catch (error) {  throw new ErrorHandler(error.message, error.status || 500);  } };  複製代碼

目前 deno_mongo 新增時只返回 _id(但願後續版本會改進這一點)

請求:

返回:

查詢所有員工:fetchAllEmployees

/**  * 全量查詢  * @param c Context  * @returns json(Employee[])  */ export const fetchAllEmployees: HandlerFunc = async (c: Context) => {  try {  const fetchedEmployees: Employee[] = await employees.find();   if (fetchedEmployees) {  const list = fetchedEmployees.length  ? fetchedEmployees.map((employee) => {  const { _id: { $oid }, name, age, salary } = employee;  return { id: $oid, name, age, salary };  })  : [];  return c.json(list, 200);  }  } catch (error) {  throw new ErrorHandler(error.message, error.status || 500);  } }; 複製代碼

指定 id 查詢員工:fetchOneEmployee

/**  * 指定 id 查詢  * @param c Context  * @returns json(Employee)  */ export const fetchOneEmployee: HandlerFunc = async (c: Context) => {  try {  const { id } = c.params as { id: string };   const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });   if (fetchedEmployee) {  const { _id: { $oid }, name, age, salary } = fetchedEmployee;  return c.json({ id: $oid, name, age, salary }, 200);  }   throw new ErrorHandler("Employee not found", 404);  } catch (error) {  throw new ErrorHandler(error.message, error.status || 500);  } }; 複製代碼

請求:

返回:

更新員工信息: updateEmployee

/**
 * 更新員工  * @param c Context  * @returns msg string  */ export const updateEmployee: HandlerFunc = async (c: Context) => {  try {  const { id } = c.params as { id: string };  if (c.request.headers.get("content-type") !== "application/json") {  throw new ErrorHandler("Invalid body", 422);  }   const body = await (c.body()) as {  name?: string;  salary: string;  age?: string;  };   if (!Object.keys(body).length) {  throw new ErrorHandler("Request body can not be empty!", 400);  }   const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });   if (fetchedEmployee) {  const { matchedCount } = await employees.updateOne(  { _id: { "$oid": id } },  { $set: body },  );  if (matchedCount) {  return c.string("Employee updated successfully!", 204);  }  return c.string("Unable to update employee");  }  throw new ErrorHandler("Employee not found", 404);  } catch (error) {  throw new ErrorHandler(error.message, error.status || 500);  } }; 複製代碼

更新成功後回返回對象包含三個字段:

  • matchedCount
  • modifiedCount
  • upsertedId

請求:

返回:

最後,刪除員工:

/**  * 刪除  * @param c Context  * @returns msg string  */ export const deleteEmployee: HandlerFunc = async (c: Context) => {  try {  const { id } = c.params as { id: string };   const fetchedEmployee = await employees.findOne({ _id: { "$oid": id } });   if (fetchedEmployee) {  const deleteCount = await employees.deleteOne({ _id: { "$oid": id } });  if (deleteCount) {  return c.string("Employee deleted successfully!", 204);  }  throw new ErrorHandler("Unable to delete employee", 400);  }   throw new ErrorHandler("Employee not found", 404);  } catch (error) {  throw new ErrorHandler(error.message, error.status || 500);  } }; 複製代碼

請求:

返回:

代碼部分完成了,如今啓動服務吧~

deno run --allow-write --allow-read --allow-plugin --allow-net --allow-env --unstable ./server.ts 
 複製代碼

爲了確保程序安全執行,deno 默認阻止任何訪問磁盤、網絡或環境變量的操做。所以,若是想要服務成功運行,你須要加上這些標記:

  • --allow-write
  • --allow-read
  • --allow-plugin
  • --allow-net
  • --allow-env

可能這個時候你會問了:我咋記得住我要加哪些標記?不用擔憂,若是缺了哪一個的話,控制檯會告訴你的。

成功運行,撒花~

Compile file:///Users/xxxxxx/deno-demo/server.ts
INFO load deno plugin "deno_mongo" from local "/Users/xxxxxx/deno-demo/.deno_plugins/deno_mongo_8834xxxxxxxxxxxxxx8a4c.dylib" server listening on http://localhost:5000 複製代碼

譯註:能夠經過 Postman 等其餘工具驗證一下接口

小結

在這篇文章裏,咱們實現了:

  • 使用 deno 構建 CRUD API 來管理員工信息
  • 使用 deno_mongo 操做 mongodb 數據庫
  • 使用簡易 deno 框架 abc 構建服務器
  • 使用 denv 聲明環境變量

你可能注意到了 deno

  • 不須要初始化 package.json 文件或者在 node_modules 目錄下安裝模塊
  • 經過 url 直接引入模塊
  • 須要Add flags to secure the execution of the program.
  • Don't install typescript locally because it's compiled in Deno.

以上,

源碼

原做者:github.com/slim-hmidi/…

譯者:github.com/xunge0613/d…

譯註:XX 學完這個後,這幾天又玩了下 Go 語言,發現 deno 好多地方和 Go 很類似,好比:

  • Go 也經過 url 引入模塊(使用版本 1.14.4
  • deno 使用原生 TypesSriptGo 自己就是強類型,必須聲明類型;而 Go 裏聲明 Struct 感受相似 TS 裏的 interface
  • 可能還有其餘的一時想不起來了😝

附錄:Mac 安裝 Deno

代碼截取自Deno 鑽研之術:(1) Hello,從多樣化安裝到簡單實戰,有修改。

友情提示:目前 Mac 用戶使用 homebrew 安裝版本只有 0.4.2,運行本文章代碼會報錯。

推薦:Mac 系統使用 curl 方式安裝高版本 deno

# 經過 curl 下載遠程的安裝腳本 install.sh 中的 deno.zip 壓縮包到本地並當即執行
$ curl -fsSL https://deno.land/x/install/install.sh | sh # Archive: /Users/${USER_NAME}/.deno/bin/deno.zip # inflating: deno # Deno was installed successfully to /Users/${USER_NAME}/.deno/bin/deno # Manually add the directory to your $HOME/.bash_profile (or similar) # export DENO_INSTALL="/Users/${USER_NAME}/.deno" # export PATH="$DENO_INSTALL/bin:$PATH" # Run '/Users/${USER_NAME}/.deno/bin/deno --help' to get started  # 輸入 deno -V 並不能運行成功 deno 命令,須要咱們手動配置環境變量來讓終端知道 deno 命令該在哪執行。 $ deno -V # zsh: command not found: deno  # 注意:${USER_NAME} 是你在本身操做系統下的用戶名,須要手動改成本身的用戶名。 $ export DENO_INSTALL="/Users/xuxun/.deno" $ export PATH="$DENO_INSTALL/bin:$PATH" $ deno -V deno 1.1.1 $ which deno /Users/xuxun/.deno/bin/deno  複製代碼

友情提示:curl 下載 deno 過程可能有點慢,能夠選擇扔在一邊先不去管它。

結語

注:本文部分翻譯自 dev.to/slimhmidi/c…,爲了便於安裝文章調試項目,因此在原文基礎上有所刪減,另外代碼部分因 Deno 及相關插件版本緣由有所改動~

由於第一次正式接觸 denoGo,因此若是有不足之處歡迎指出~

本文排版用了 Markdown Nice,很好用

對了,以後若是文章內容有更新,會在我博客上進行更新~

參考資料

[1]

Create a server with deno and mongo: https://dev.to/slimhmidi/create-a-server-with-deno-and-mongo-206l

[2]

Deno 安裝文檔: https://github.com/denoland/deno_install

[3]

Deno 鑽研之術:(1) Hello,從多樣化安裝到簡單實戰: https://juejin.im/post/5ebb8b9c5188250bdf5c2d89

相關文章
相關標籤/搜索