使用Deno和Postgres创建您的第一个REST API

2020年12月30日11:08:36 发表评论 45 次浏览

本文概述

由Node.js背后的人创造, 天野同样在开发人员中越来越受欢迎。

在节点无法提供的功能(例如安全性, 模块和依赖项)日趋成熟和发展之后, Deno被证明与前代产品一样强大。

它基本上是基于健壮的TypeScript运行时Google V8引擎。但请放心, Deno还支持普通JavaScript, 这是我们将在本文中使用的。

Deno是在以下条件下创建的:

首先, 它是安全的, 这意味着其默认执行基于沙盒环境。

从运行时无法访问网络, 文件系统等内容。当你的代码尝试访问这些资源时, 系统会提示你允许操作。

它通过URL加载模块(如浏览器)。这使你可以将分散的代码用作模块并将其直接导入源代码, 而不必担心注册表中心

它也与浏览器兼容。例如, 如果你使用ES模块, 则不必担心Webpack或Gulp的使用。

此外, 它基于TypeScript。

如果你已经使用TypeScript, 那么它非常适合你:非常简单, 并且不需要额外的设置。如果你不使用TypeScript, 那没问题。你也可以将其与纯JavaScript一起使用。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用Deno和Postgres创建您的第一个REST API1

你可以阅读更多有关它的内容这里并在官方文件.

在本文中, 我们将重点放在操作方法上。

具体来说, 我们将介绍如何仅使用JavaScript, Deno和与Postgres数据库的连接从头开始创建API。

我们将开发的应用程序是啤酒领域的基本CRUD。

设置东西

首先, 你需要设置工具和一切。对于本文, 你需要:

  • 你选择的IDE –我们将使用VS Code
  • 一种Postgres服务器和你喜欢的GUI工具进行管理
  • 迪诺

安装Deno很简单但是, 由于Deno不断更新, 因此我们将重点放在版本1.0.0, 因此无论发布了哪些新功能, 它都将始终有效。

为此, 请运行以下命令:

// With Shell
curl -fsSL https://deno.land/x/install/install.sh | sh -s v1.0.0
// With PowerShell
$v="1.0.0"; iwr https://deno.land/x/install/install.ps1 -useb | iex

然后, 运行命令deno --version检查安装是否正常。

你应该会看到以下内容:

deno 1.0.0
v8 8.4.300
typescript 3.9.2

项目结构

接下来, 让我们创建项目结构, 包括初始文件和文件夹。在你喜欢的文件夹中, 创建与下图所示相同的结构:

项目结构的图像,显​​示您已安装的Deno版本。

检查Deno版本

结构可以描述如下:

控制器:保存用于处理到达请求的JS文件, 对服务的进一步调用以及下面的层, 最后是响应的传递。所有这些对象都是从Deno继承的, 因此你不必担心是否需要手动处理请求/响应。

db:存放我们创建SQL脚本并直接连接到Postgres数据库的文件夹。

仓库:这些JS文件将处理数据库操作的管理。每个创建, 删除或更新都将按照其逻辑在此处进行。

服务:这些文件将处理我们的业务的业务逻辑, 例如验证, 数据转换等。

应用程序

让我们从第一个也是最重要的文件的代码开始, index.js.

看下面的代码:

import { Application } from "https://deno.land/x/oak/mod.ts";
import { APP_HOST, APP_PORT } from "./config.js";
import router from "./routes.js";
import _404 from "./controllers/404.js";
import errorHandler from "./controllers/errorHandler.js";

const app = new Application();

app.use(errorHandler);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(_404);

console.log(`Listening on port:${APP_PORT}...`);

await app.listen(`${APP_HOST}:${APP_PORT}`);

我们需要一个Web框架来处理请求和响应处理, 线程管理, 错误等的详细信息。

对于Node, 通常使用表现or考阿以此目的。

但是, 正如我们所见, Deno不支持Node库。

我们需要使用另一种灵感来自Koa的橡木:用于Deno网络服务器的中间件框架。请注意, 我们使用的是Oak的4.0.0版本。这是Deno 1.0.0的正确版本。如果你不提供版本, Deno将始终获取最新版本, 这可能会导致重大更改并弄乱你的项目。所以要小心!

它具有受Koa启发的中间件框架, 而其中间件路由器则受Koa启发路由器.

如代码清单所示, 它的用法与Express非常相似。在第一行中, 我们直接从地域网址。

其余的导入将被进一步配置。

的应用课堂是一切从Oak开始的地方。

我们实例化它并添加错误处理程序, 控制器, 路由系统, 并最终调用该方法听()启动服务器传递URL(主机+端口)。

在这里你可以看到的代码config.js(将其放置在项目的根目录中):

export const APP_HOST = Deno.env.get("APP_HOST") || "127.0.0.1";
export const APP_PORT = Deno.env.get("APP_PORT") || 4000;

到目前为止很熟悉, 不是吗?现在转到路由。

与Express一样, 我们需要建立路由器, 将请求重定向到适当的JavaScript函数, 这些JavaScript函数依次处理它们, 存储或搜索数据并返回结果。

看一下代码routes.js(也在根文件夹中):

import { Application } from "https://deno.land/x/oak@v4.0.0/mod.ts";
import getBeers from "./controllers/getBeers.js";
import getBeerDetails from "./controllers/getBeerDetails.js";
import createBeer from "./controllers/createBeer.js";
import updateBeer from "./controllers/updateBeer.js";
import deleteBeer from "./controllers/deleteBeer.js";

const router = new Router();

router
  .get("/beers", getBeers)
  .get("/beers/:id", getBeerDetails)
  .post("/beers", createBeer)
  .put("/beers/:id", updateBeer)
  .delete("/beers/:id", deleteBeer);

export default router;

到目前为止, 还没有任何工作。不用担心-我们仍需要在启动项目之前对其进行配置。

最后的清单显示Oak也将为我们处理路由系统。

的路由器更具体地说, 将实例化类, 以允许对每个HTTP GET, POST, PUT和DELETE操作使用相应的方法。

文件开头的导入对应于将处理各自请求的每个功能。

你可以决定是采用这种方式还是希望将所有内容都放在同一控制器文件中。

数据库和存储库

在继续执行更多JavaScript代码之前, 我们需要设置数据库。

确保已在本地主机上安装并运行Postgres服务器。连接到它, 并创建一个新数据库, 名为notlogy_deno.

然后输入。在里面public模式, 运行以下创建脚本:

CREATE TABLE IF NOT EXISTS beers (
    id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL, brand VARCHAR(50) NOT NULL, is_premium BOOLEAN, registration_date TIMESTAMP
)

该脚本也可以在/D b我的项目版本的文件夹。

它创建一个新表, "啤酒"以存储CRUD的值。

请注意, 主键是自动递增的(通过序列号关键字)以利用ID生成策略来促进我们的工作。

现在, 让我们创建一个文件来处理与Postgres的连接。

在里面db文件夹, 创建database.js文件并添加以下内容:

import { Client } from "https://deno.land/x/postgres/mod.ts";

class Database {
  constructor() {
    this.connect();
  }

  async connect() {
   this.client = new Client({
      user: "postgres", database: "notlogy_deno", hostname: "localhost", password: "postgres", port: 5432
    });

    await this.client.connect();
  }
}

export default new Database().client;

确保根据你的Postgres配置调整连接设置。配置非常简单。

迪诺创造了它邮递区号(Deno的PostgreSQL驱动程序)基于node-postgres和pg.

如果你是Node用户, 那么你将熟悉语法。

请注意, 设置会根据你使用的数据库而略有变化。

在这里, 我们将设置对象作为客户参数。

但是, 在MySQL中, 它直接进入connect()功能。

在 - 的里面仓库文件夹, 我们将创建文件beerRepo.js, 它将托管存储库, 以通过我们上面所吃的文件访问数据库。

这是它的代码:

import client from "../db/database.js";

class BeerRepo {
  create(beer) {
    return client.query(
      "INSERT INTO beers (name, brand, is_premium, registration_date) VALUES ($1, $2, $3, $4)", beer.name, beer.brand, beer.is_premium, beer.registration_date
    );
  }

  selectAll() {
    return client.query("SELECT * FROM beers ORDER BY id");
  }

  selectById(id) {
    return client.query(`SELECT * FROM beers WHERE id = $1`, id);
  }
update(id, beer) {
    var latestBeer = this.selectById(id);
    var query = `UPDATE beers SET name = $1, brand = $2, is_premium = $3 WHERE id = $4`;

    return client.query(query, beer.name !== undefined ? beer.name : latestBeer.name, beer.brand !== undefined ? beer.brand : latestBeer.brand, beer.is_premium !== undefined ? beer.is_premium : latestBeer.is_premium, id);
}

export default new BeerRepo();

导入database.js连接到数据库的文件。

然后, 文件的其余部分只是类似数据库的CRUD操作。来看看他们。米

与其他主要数据库框架一样, 为了防止SQL注入, Deno允许我们也将参数传递给SQL查询。

同样, 每个数据库都有其自己的语法。

例如, 在Postgres中, 我们使用美元符号, 然后按其特定顺序使用参数的编号。

这里的顺序非常重要。在MySQL中, 运算符是问号(?)。

每个参数的值紧随其后, 例如可变参数参数(在Postgres中:对于MySQL, 它将是一个数组)。

每个项目必须与其相应的查询运算符完全在同一位置。

的查询()函数是我们每次要访问或更改数据库中的数据时都会使用的函数。

我们还将特别注意我们的更新方法。

这是可以用来仅更新用户正在传递的内容的众多可能策略之一。

在这里, 我们首先通过ID提取用户, 并将其存储到本地变量中。然后, 当检查每个啤酒属性是否在请求有效载荷之内时, 我们可以决定是否使用它或存储的值。

服务

我们的存储库已设置。

现在, 让我们进入服务层。

里面的服务文件夹, 创建文件beerService.js并添加以下代码:

import beerRepo from "../repositories/beerRepo.js";

export const getBeers = async () => {
  const beers = await beerRepo.selectAll();

  var result = new Array();

  beers.rows.map(beer => {
    var obj = new Object();

    beers.rowDescription.columns.map((el, i) => {
      obj[el.name] = beer[i];
    });
    result.push(obj);
  });

  return result;
};

export const getBeer = async beerId => {
  const beers = await beerRepo.selectById(beerId);

  var obj = new Object();
  beers.rows.map(beer => {
    beers.rowDescription.columns.map((el, i) => {
      obj[el.name] = beer[i];
    });
  });

  return obj;
};

export const createBeer = async beerData => {
  const newBeer = {
    name: String(beerData.name), brand: String(beerData.brand), is_premium: "is_premium" in beerData ? Boolean(beerData.is_premium) : false, registration_date: new Date()
  };

  await beerRepo.create(newBeer);

  return newBeer.id;
};

export const updateBeer = async (beerId, beerData) => {
  const beer = await getBeer(beerId);

  if (Object.keys(beer).length === 0 && beer.constructor === Object) {
    throw new Error("Beer not found");
  }

  const updatedBeer = {
    name: beerData.name !== undefined ? String(beerData.name) : beer.name, brand: beerData.brand !== undefined ? String(beerData.brand) : beer.brand, is_premium:
      beerData.is_premium !== undefined
        ? Boolean(beerData.is_premium)
        : beer.is_premium
  };

  beerRepo.update(beerId, updatedBeer);
};

export const deleteBeer = async beerId => {
  beerRepo.delete(beerId);
};

这是我们拥有的最重要的文件之一。

我们在这里与存储库进行交互, 并接收来自控制器的调用。

每种方法还对应于一种CRUD操作, 并且由于Deno数据库本质上固有地是异步的, 因此它总是返回一个promise。

这就是为什么我们需要等待直到在我们的同步代码中完成。

另外, 退货是与我们的确切业务对象不符的对象啤酒, 因此我们必须将其转换为可理解的JSON对象。

getBeers将始终返回一个数组, 并且getBeer将始终返回单个对象。

两种功能的结构非常相似。

的啤酒result是一个数组数组, 因为它封装了查询可能返回的列表, 并且每个返回值也是一个数组(假设每个列值都在该数组内)。

rowDescription, 依次存储结果包含的每一列的信息(包括名称)。

其他一些功能(例如验证)也在此处进行。

在里面updateBeer功能, 你会看到我们一直在检查给定的beerId实际上, 在进行更新之前数据库中已经存在。

否则, 将引发错误。随意添加所需的任何验证或其他代码。

控制器

现在是时候为我们的请求和响应创建处理程序了。

输入和输出验证可以更好地坚持这一层。

让我们从错误管理文件开始-我们已经在index.js.

在里面控制器文件夹, 创建文件404.js和errorHandler.js.

代码为404.js:

export default ({ response }) => {
  response.status = 404;
  response.body = { msg: "Not Found" };
};

代码为errorHandler.js:

export default async ({ response }, nextFn) => {
  try {
    await nextFn();
  } catch (err) {
    response.status = 500;
    response.body = { msg: err.message };
  }
};

他们很简单。在第一个中, 我们只是导出一个函数, 该函数将在抛出异常时处理业务异常, 例如HTTP 404。

第二个将处理应用程序生命周期中可能发生的任何其他类型的未知错误, 将其视为HTTP 500, 并在响应正文中发送错误消息。

现在, 让我们进入控制器。让我们从吸气剂开始。

这是内容getBeers.js:

import { getBeers } from "../services/beerService.js";

export default async ({ response }) => {
  response.body = await getBeers();
};

每个控制器操作必须是异步的。

每个控制器操作都接收一个或两个请求和响应对象作为参数。

它们会被Oak API拦截并经过预处理, 然后才能到达控制器或返回给客户端调用程序。

无论你使用哪种逻辑, 都不要忘记设置响应主体, 因为它是你请求的结果。

以下是内容getBeerDetails.js:

import { getBeer } from "../services/beerService.js";

export default async ({
  params, response
}) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };
    return;
  }

  const foundBeer = await getBeer(beerId);
  if (!foundBeer) {
    response.status = 404;
    response.body = { msg: `Beer with ID ${beerId} not found` };
    return;
  }

  response.body = foundBeer;
};

此内容类似于我们的内容getbeers.js, 除了验证。

由于我们收到了beerId作为参数, 最好检查它是否已满。如果该参数的值不存在, 请在正文中发送相应的消息。

下一步是创建文件。

这是文件的内容createBeer.js:

import { createBeer } from "../services/beerService.js";

export default async ({ request, response }) => {
  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid beer data" };
    return;
  }

  const {
    value: { name, brand, is_premium }
  } = await request.body();

  if (!name || !brand) {
    response.status = 422;
    response.body = { msg: "Incorrect beer data. Name and brand are required" };
    return;
  }

  const beerId = await createBeer({ name, brand, is_premium });

  response.body = { msg: "Beer created", beerId };
};

同样, 进行一些验证以确保输入数据关于必填字段有效。验证还确认请求附带了主体。

呼吁createBeer服务函数分别传递每个参数。如果啤酒对象的属性数量增加, 则保持这种功能将是不明智的。

你可以改为使用模型对象, 该模型对象将存储啤酒的每个属性, 并在控制器和服务方法中传递。

这是我们的updateBeer.js内容:

import { updateBeer } from "../services/beerService.js";

export default async ({ params, request, response }) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };
    return;
  }

  if (!request.hasBody) {
    response.status = 400;
    response.body = { msg: "Invalid beer data" };
    return;
  }

  const {
    value: { name, brand, is_premium }
  } = await request.body();

  await updateBeer(beerId, { name, brand, is_premium });

  response.body = { msg: "Beer updated" };
};

如你所见, 它几乎具有相同的结构。区别在于params配置。

由于我们不允许更新啤酒的每个属性, 因此我们限制了将归入服务层的属性。

的beerId还必须是第一个参数, 因为我们需要确定要更新的数据库元素。

最后, 我们的代码deleteBeer.js:

import { deleteBeer, getBeer } from "../services/beerService.js";

export default async ({
  params, response
}) => {
  const beerId = params.id;

  if (!beerId) {
    response.status = 400;
    response.body = { msg: "Invalid beer id" };
    return;
  }

  const foundBeer = await getBeer(beerId);
  if (!foundBeer) {
    response.status = 404;
    response.body = { msg: `Beer with ID ${beerId} not found` };
    return;
  }

  await deleteBeer(beerId);
  response.body = { msg: "Beer deleted" };
};

注意它与其他相似。

同样, 如果你觉得它过于重复, 则可以将这些控制器代码混合到一个控制器文件中。

例如, 这将使你的代码更少, 因为通用代码将在一个函数中在一起。

现在让我们对其进行测试。

要运行Deno项目, 请转到提示命令行。在根文件夹中, 发出以下命令:

deno run --allow-net --allow-env index.js

请记住, Deno使用安全资源。因此, 这意味着要允许HTTP调用和访问env变量, 我们需要明确要求。这些标志分别完成任务。

日志将显示Deno正在下载项目所需的所有依赖项。消息"正在侦听端口:4000 ..."必须出现。

为了测试API, 我们将利用邮差实用工具。随意使用你喜欢的任何一种。

这是一个实际的POST创建示例:

POST创建的示例。

在我们的Deno API中创建啤酒

之后, 转到GET操作以列出数据库中的所有啤酒:

GET操作用于列出数据库中的所有啤酒。

列出数据库中的所有啤酒。

总结

我会将其余的运行测试留给你。

你也可以转到数据库, 然后直接从那里检查要插入, 更新或删除的值。

可以找到本教程的最终代码这里.

请注意, 我们已经完成了完整的类似于CRUD的功能性API, 而无需使用Node.js或node_modules目录(因为Deno维护缓存中的依赖项)。

每次你要使用依赖项时, 只需通过代码进行声明即可, Deno会负责下载它(不需要package.json文件)。

另外, 如果你想将其与TypeScript一起使用, 也无需安装它。

只需继续并立即开始使用TypeScript进行编码。

日志火箭:全面了解你的网络应用

LogRocket仪表板免费试用横幅

日志火箭是一个前端应用程序监视解决方案, 可让你重播问题, 就好像问题发生在你自己的浏览器中一样。 notlogy无需猜测错误发生的原因, 也不要求用户提供屏幕截图和日志转储, 而是让你重播会话以快速了解出了什么问题。无论框架如何, 它都能与任何应用完美配合, 并具有用于记录来自Redux, Vuex和@ ngrx / store的其他上下文的插件。

除了记录Redux动作和状态外, notlogy还会记录控制台日志, JavaScript错误, 堆栈跟踪, 带有标题+正文, 浏览器元数据和自定义日志的网络请求/响应。它还使用DOM来记录页面上的HTML和CSS, 甚至可以为最复杂的单页面应用程序重新创建像素完美的视频。

免费试用

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: