Первоначально опубликовано на deno.com/blog.

Существует множество отличных руководств для всех, кто хочет начать создавать REST API с помощью TypeScript и Express. У этих руководств, какими бы замечательными они ни были, есть два недостатка:

  1. Они требуют, чтобы вы установили и настроили TypeScript и предоставили все необходимое для этого. Это может занять много времени и стать источником разочарования, особенно для новых разработчиков.
  2. Они не учитывают необходимость проявлять инициативу в отношении упаковки ненадежного кода; это неудивительно, так как большинство инструментов не поддерживают его.

Вот почему мы создали этот учебник. С Deno вам не нужно настраивать TypeScript, поэтому вы можете приступить к работе с минимальными зависимостями.

Не стесняйтесь посмотреть видеопрохождение этого поста.

Если вы хотите перейти к коду, вы можете сделать это здесь.

Настройка Express и его виды

Давайте создадим main.ts, который будет содержать логику для нашего API.

В этом файле давайте импортируем Express через спецификатор npm.

import express, { NextFunction, Request, Response } from "npm:[email protected]";

Это дает нам выражение, но не определения типов. Давайте импортируем определения типов, добавив этот комментарий:

// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:[email protected]";

Затем нам нужно определить способ взаимодействия с интерфейсом приложения Express, и нам нужно будет определить порт для его запуска, который мы получим из среды:

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

Давайте определим тестовый маршрут, который будет приветствовать при получении запроса GET, который мы сейчас просто сделаем базовым маршрутом по умолчанию:

app.get("/", (_req, res) => {
  res.status(200).send("Hello from Deno and Express!");
});

Теперь мы построили простую логику, нам просто нужно, чтобы она слушала и начинала обслуживать запросы! Для этого мы будем использовать .listen(), как показано ниже:

app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

И теперь мы готовы к работе!

Безопасный запуск сервера

Запускаем наш сервер:

Когда мы разрабатываем API, нам приходится использовать все виды кода, от геоинформации, ИИ, рекламных серверов и любых других входных данных, которые должны собраться вместе, чтобы создать то, что требуется. Конечно, мы не ожидаем, что Express создаст уязвимости, но Express — это лишь часть стека, который вам понадобится, чтобы что-то сделать.

Если бы он запросил доступ к системной информации, таймерам с высоким разрешением или доступ за пределы каталога, это было бы тревожным сигналом. Вы можете указать разрешения различными способами, включая шебанг в сценариях.

На данный момент у нас есть работающая служба API, которую мы можем запросить с помощью curl:

Теперь мы уверены, что фреймворк работает правильно, поэтому мы уверены в нашей установке и во всем остальном. Но пока это не очень хорошая рабочая среда, поэтому давайте настроим наш файл deno.jsonc, чтобы определить несколько вспомогательных скриптов:

Это работает аналогично package.json скриптам (на самом деле Deno может использовать даже package.json скрипты, но рекомендуется deno.jsonc), где у нас одна задача на разработку, а другая на запуск сервера без просмотра и перезагрузки по изменениям.

Увидев вывод deno task, мы можем подтвердить, что у нас есть два доступных скрипта:

$ deno task
Available tasks:
- dev
    deno run --allow-read --allow-net --allow-env --watch main.ts
- start
    deno run --allow-read --allow-net --allow-env main.ts

Мы можем использовать deno task dev и deno task start соответственно.

Добавление ведения журнала

Следующее, что нам понадобится, — это какая-то функция ведения журнала, чтобы мы могли устранять неполадки в наших запросах во время их создания, и это отличное введение в концепцию промежуточного программного обеспечения в Express.

Промежуточное ПО — это функция, которая может читать и даже изменять объекты req и res. Мы используем промежуточное программное обеспечение, чтобы делать все, от ведения журнала до внедрения заголовков или даже ограничения скорости и проверки авторизации. Промежуточное программное обеспечение должно сделать одну из двух вещей, когда это будет сделано:

  • Он должен закрыть соединение с ответом, если это уместно, или
  • Он должен вызвать next(), который сообщает Express, что пора передать объекты следующей промежуточной функции.

Промежуточное ПО принимает 3 аргумента: req и res, как и следовало ожидать, а также next, который указывает на следующую подходящую функцию промежуточного ПО (или возвращает управление функции-обработчику).

Вместо того, чтобы console.log() что-то в каждом обработчике, который мы пишем, давайте определим первую промежуточную функцию как регистратор и скажем Express, что мы хотели бы ее использовать. В main.ts:

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

Вы можете иметь столько промежуточного программного обеспечения, сколько захотите, и организовать его удобным для вас способом. Просто помните, что скорость ваших ответов зависит от того, насколько быстро ваша цепочка промежуточного программного обеспечения возвращает управление фреймворку. Промежуточное ПО выполняется в том порядке, в котором о нем сообщается фреймворку.

Генерация данных

Так что сейчас мы находимся в отличном месте, чтобы начать разработку. Запустите команду ./generate_data.ts (deno run -A ./generate_data.ts, если шебанг не сработает для вас), которая сгенерирует некоторые фиктивные пользовательские данные в data_blob.json, которые мы можем безопасно использовать, как и любое другое хранилище данных только для чтения, через утверждения типа импорта Deno:

import demoData from "./data_blob.json" assert { type: "json" };

Теперь у нас есть доступ к demoData.users в наших обработчиках, так что давайте напишем два обработчика:

  • один /users, который возвращает все содержимое объекта пользователей, и
  • дополнительный динамический маршрут, который позволяет нам искать одного пользователя по идентификатору
app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});

app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});

Мы также можем убрать маршрут по умолчанию hello world, что оставляет нам хорошую отправную точку для API:

// @deno-types="npm:@types/express@4"
import express, { NextFunction, Request, Response } from "npm:[email protected]";
import demoData from "./data_blob.json" assert { type: "json" };

const app = express();
const port = Number(Deno.env.get("PORT")) || 3000;

const reqLogger = function (req, _res, next) {
  console.info(`${req.method} request to "${req.url}" by ${req.hostname}`);
  next();
};

app.use(reqLogger);
app.get("/users", (_req, res) => {
  res.status(200).json(demoData.users);
});
app.get("/users/:id", (req, res) => {
  const idx = Number(req.params.id);
  for (const user of demoData.users) {
    if (user.id === idx) {
      res.status(200).json(user);
    }
  }
  res.status(400).json({ msg: "User not found" });
});
app.listen(port, () => {
  console.log(`Listening on ${port} ...`);
});

Обратите внимание, что обработчик Hello, world! для / был удален (и отсутствует в связанном репозитории).

Что дальше?

У нас есть отличная отправная точка для REST API, состоящая менее чем из 30 строк кода. Теперь вы можете добавить обработчик POST с помощью app.post(), обработчик PUT с app.put() или любые другие методы, которые вы хотите.

В следующем посте мы расскажем, как мы можем использовать инструменты запуска тестов и тестов Deno, чтобы нам было удобнее переводить наш код из проверки концепции в то, чему мы будем доверять в производстве. После этого мы закончим серию тем, как мы можем развернуть наш проект.

Застрял? Получите помощь в нашем Discord!