Deno + Oak Build Cool Todo API

deno

Преамбула

Я разработчик JavaScript/Node с молчаливой любовью и даже восхищением Deno. Deno очаровал меня с самого начала, и с тех пор я стал большим поклонником Deno, с нетерпением ожидая, когда однажды смогу официально поиграть в Deno.

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

Если вы хотите в любое время просмотреть код этой статьи или обратиться к нему, вы можете посетить этот мой репозиторий:@adeelibr/deno-playground, который содержит все коды серии.

Примечание переводчика: скоро будет переведена еще одна статья "Как использовать MySQL с Deno и Oak", а связанная с ней демонстрация также будет включена в "Deno Research Techniques".

фото изBernard de Clerk / Unsplash

О чем пойдет речь в этой статье

  • Создайте базовый сервер
  • Создайте 5 API (маршруты/контроллер)
  • Создайте промежуточное программное обеспечение, чтобы добавить ведение журнала выходных данных терминала в запросы API.
  • Создайте промежуточное ПО 404 для обработки, когда пользователь обращается к неизвестному API.

Знания, необходимые для этой статьи

  • Уже установленная среда Deno (не бойтесь, я покажу как)
  • Базовое понимание TypeScript
  • Если у вас есть некоторое представление о Node/Express ранее, будет лучше (не имеет значения, если у вас нет, эта статья все еще очень проста для понимания)

Давайте начнем

Сначала нам нужно установить Deno. Поскольку я использую Mac OS, здесь я буду использовать brew. Просто откройте терминал и введите эту команду:

$ brew install deno

Но если вы используете другую операционную систему, вот руководство по установке:deno.land installation. Существуют различные методы установки, которые вы можете выбрать в соответствии с различными операционными системами.

После успешной установки закройте терминал и откройте другой, введите эту команду:

$ deno --version

Если все в порядке, терминал выдаст следующий вывод:

deno --versionКоманда используется для проверки того, какая версия Deno установлена ​​в данный момент.

Превосходно! С этим введением мы успешно выполнили 10% задач в этой статье.

Давайте продолжим и создадим серверный API для нашего приложения со списком дел.

Подготовка к проекту

Прочитайте следующее, вы можете прийти на склад заранее, чтобы увидеть все коды, включенные в эту статью:@adeelibr/deno-playground.

Здесь мы начинаем с нуля:

  • Создатьchapter_1:oak(вы можете назвать его как хотите).
  • использовать, когда вы закончите создаватьcdкоманду в эту папку. Создатьserver.tsфайл и заполните его следующим кодом:
import { Application } from "https://deno.land/x/oak/mod.ts";

const app = new Application();
const port: number = 8080;

console.log('running on port ', port);
await app.listen({ port });

Сначала запустим этот файл. После открытия терминала и входа в корневой каталог текущего проекта введите следующую команду:

$ deno run --allow-net server.ts

Не волнуйтесь, я представлю это позже--allow-netЧто именно делает параметр 😄.

Если ничего другого, вы получите следующие результаты:

На данный момент мы создали серверное приложение, прослушивающее порт 8080. Это приложение может работать нормально, только если порт 8080 не занят.

Если у вас есть опыт разработки с помощью JavaScript, вы могли заметить, что способ импорта модулей немного отличается. Здесь мы импортируем модуль следующим образом:

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

когда вы выполняете в терминалеdeno run ---allow-net <file_name>По команде Deno прочитает ваш импорт и установит эти модули, если они еще не установлены в локальной глобальной среде.

Deno попытается получить доступ при первом выполненииhttps://deno.land/x/oak/mod.tsмодуль и установитьoakбиблиотека. Oak — это веб-фреймворк Deno, ориентированный на написание API.

Следующую строку пишем так:

const app = new Application();

Этот оператор создает экземпляр нашего приложения, что является краеугольным камнем глубокого погружения в Deno в этой статье. Вы можете добавлять маршруты к этому экземпляру, настраивать промежуточное ПО (например, промежуточное ПО для ведения журналов), писать обработчики 404 неизвестных маршрутов и многое другое.

Далее пишем это:

const port: number = 8080;
// const port = 8080; // => 也可以写成这样

Вышеупомянутые две строки функционально эквивалентны, единственная разницаconst port: number = 8080Скажите TypeScript:portТип переменной числовой.

Если написать так:const port: number = "8080", терминал выдаст такую ​​ошибку: Переменная порта должна бытьnumberтипа, но такие попытки используютstringВведите «8080», чтобы присвоить ему значение.

Если вы хотите узнать больше о Type, вы можете ознакомиться с этой простой документацией прямо сейчас:Официальный TypeScript — основные типы. Чтобы вернуться к этой статье, потребуется всего 2–3 минуты.

В конце файла пишем:

console.log('running on port ', port);
await app.listen({ port });

Как и выше, мы позволяем Deno прослушивать порт 8080, а номер порта жестко запрограммирован.

В твоемserver.tsДобавьте следующий дополнительный код в файл:

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

const app = new Application();
const port: number = 8080;

const router = new Router();
router.get("/", ({ response }: { response: any }) => {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());

console.log('running on port ', port);
await app.listen({ port });

По сравнению с предыдущими дополнениями отoakтакже импортируется вApplicationа такжеRouterПеременная.

из них оRouterСоответствующий код:

const router = new Router();
router.get("/", ({ response }: { response: any }) => {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());

мы проходимconst router = new Router()оператор создает новый пример Router, затем мы его укореняем/созданный дескрипторgetКак выполняется запрос.

Давайте сосредоточимся на следующем:

router.get("/", ({ response }: { response: any }) => {
  response.body = {
    message: "hello world",
  };
});

router.getФункция принимает два параметра. Первый параметр - это путь монтирования маршрута/, второй параметр является функцией. Сама функция также принимает параметр объекта, который здесь деконструируется с использованием синтаксиса ES6, и берется только значение переменной ответа.

Далее пишется как преждеconst port: number = 8080;то же предложение, что иresponseТип объявления переменной.{ response }: { response: any }оператор сообщает TypeScript, что мы здесь деструктурируемresponseпеременнаяanyТип.

anyТипы могут помочь вам избежать строгой проверки типов TypeScript, которую вы можете сделать с помощьюэтот документУзнать больше.

Далее я написал, что нужно использоватьresponseпеременная и установитьresponse.body.message = "hello world";.

response.body = {
  message: "hello world",
};

И последнее, но не менее важное: мы написали следующие две строки кода:

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

Первая строка указывает Deno включить все пути, установленные в нашей переменной маршрутизатора (сейчас мы устанавливаем только корневой путь), а вторая строка сообщает Deno разрешить любому методу доступа запрашивать заданный нами путь, напримерGET, POST, PUT, DELETE.

На этом этапе вы можете выполнить тестовый запуск ✅ , давайте выполним эту строку, чтобы посмотреть, что произойдет в конце:

$ deno run --allow-net server.ts

---allow-netПараметр сообщает Deno, что пользователь предоставил этому приложению разрешение на доступ к сети через открытый порт.

Теперь откройте в своем любимом браузереhttp://localhost:8080адрес, вы можете получить следующие результаты:

Браузер открывает результат выполнения localhost:8080

Самая сложная часть почти сделана, но мы только на 60% ознакомились с концепцией.

Одобрение Мастера Йоды

Отлично.

Прежде чем мы сможем начать писать API для списка дел, последнее, что нам нужно сделать, это добавить следующий код:

console.log('running on port ', port);
await app.listen({ port });

Замените его на это:

app.addEventListener("listen", ({ secure, hostname, port }) => {
  const protocol = secure ? "https://" : "http://";
  const url = ${protocol}${hostname ?? "localhost"}:${port};
  console.log(Listening on: ${port});
});

await app.listen({ port });

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

В замененной версии проходимapp.addEventListener("listen", ({ secure, hostname, port }) => {}оператор, чтобы добавить прослушиватель событий к экземпляру приложения, а затем позволить приложению прослушивать порт.

Первый параметр слушателя — это событие, которое мы хотим прослушивать. Двойной смысл, что здесь слушаютlistenСобытия 😅. Второй параметр — это объект, который можно разобрать, здесь разобранный{ secure, hostname, port }три переменные. Переменная Secure имеет логический тип, переменная hostname имеет строковый тип, а переменная port имеет числовой тип.

Если приложение запущено в это время, журнал успешного мониторинга будет выведен только после успешного мониторинга указанного порта.

Мы можем пойти еще дальше и сделать его еще более красочным. впусти насserver.tsДобавьте новый модуль, подобный этому, вверху файла:

import { green, yellow } from "https://deno.land/std@0.53.0/fmt/colors.ts";

Затем мы можем поместить следующий код в предыдущую функцию прослушивателя событий:

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

Заменить:

console.log(${yellow("Listening on:")} ${green(url)});

Далее, когда мы выполняем:

$ deno run --allow-net server.ts

Будет распечатан следующий журнал:

Так здорово, теперь у нас есть красочная консоль.

Если вы где-то застряли, вы можете перейти непосредственно к репозиторию исходного кода для этого руководства:@adeelibr/deno-playground.

Давайте создадим API для списка дел.

  • Создайте проект в корневом каталоге проектаroutesпапку, а затем создайтеtodo.tsдокумент.
  • При этом создатьcontrollersпапку, а затем создать папку в папкеtodo.tsдокумент.

Давайте сначала заполнимcontrollers/todo.tsСодержимое файла:

export default {
  getAllTodos: () => {},
  createTodo: async () => {},
  getTodoById: () => {},
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

Здесь мы просто экспортируем объект, содержащий ряд именованных функций, которые в данный момент пусты.

следующий вroutes/todo.tsЗаполните файл этим:

import { Router } from "https://deno.land/x/oak/mod.ts";
const router = new Router();
// controller 控制器
import todoController from "../controllers/todo.ts";
router
  .get("/todos", todoController.getAllTodos)
  .post("/todos", todoController.createTodo)
  .get("/todos/:id", todoController.getTodoById)
  .put("/todos/:id", todoController.updateTodoById)
  .delete("/todos/:id", todoController.deleteTodoById);

export default router;

Приведенный выше стиль кода должен быть знаком всем, кто писал Node и Express.

в том числе изoakимпортировано вRouteпеременная и пройтиconst router = new Router();оператор для его создания.

Затем мы импортируем наш контроллер:

import todoController from "../controllers/todo.ts";

Здесь следует отметить, что каждый раз, когда мы импортируем локальный файл в проект в Deno, мы должны заполнить суффикс файла. В противном случае Deno не знает суффикс файла, который пользователь хочет импортировать..js еще.tsконец.

Затем мы настраиваем все пути RESTful, которые нам нужны для приложения, с помощью следующего кода.

router
  .get("/todos", todoController.getAllTodos)
  .post("/todos", todoController.createTodo)
  .get("/todos/:id", todoController.getTodoById)
  .put("/todos/:id", todoController.updateTodoById)
  .delete("/todos/:id", todoController.deleteTodoById);

Приведенный выше код разрешит путь к этому:

метод запроса API-маршрутизация
GET /todos
GET /todos/:id
POST /todos
PUT /todos/:id
DELETE /todos/:id

Наконец мы проходимexport default router;Оператор для экспорта настроенного маршрута.

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

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

так что вернемся кserver.tsфайл делаем так:

  • Добавьте эту строку кода в начало файла:
// routes 路由
import todoRouter from "./routes/todo.ts";
  • Удалите этот фрагмент кода:
const router = new Router();
router.get("/", ({ response }: { response: any }) => {
  response.body = {
    message: "hello world",
  };
});
app.use(router.routes());
app.use(router.allowedMethods());
  • Замените его на:
app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

Наконец-то получил, твойserver.tsТеперь это должно выглядеть так:

import { Application } from "https://deno.land/x/oak/mod.ts";
import { green, yellow } from "https://deno.land/std@0.53.0/fmt/colors.ts";

// routes
import todoRouter from "./routes/todo.ts";

const app = new Application();
const port: number = 8080;

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

app.addEventListener("listen", ({ secure, hostname, port }) => {
  const protocol = secure ? "https://" : "http://";
  const url = `${protocol}${hostname ?? "localhost"}:${port}`;
  console.log(
    `${yellow("Listening on:")} ${green(url)}`,
  );
});

await app.listen({ port });

Если вы где-то застряли, вы можете перейти непосредственно к репозиторию исходного кода для этого руководства:@adeelibr/deno-playground.

Поскольку в настоящее время на маршрутизируемом контроллере нет функций, давайте вручную добавим функциональность в наш контроллер.

Перед этим мы должны создать два (небольших) файла:

  • В корневом каталоге проекта создайтеinterfacesпапку и создайтеTodo.ts(Обязательно пишите Todo с большой буквы, потому что если вы этого не сделаете, здесь не будет никаких синтаксических ошибок — это просто соглашение).
  • При этом создатьstubsпапку и создайтеtodos.tsдокумент.

существуетinterfaces/Todo.tsВ файле прописано следующее описание интерфейса:

export default interface Todo {
  id: string,
  todo: string,
  isCompleted: boolean,
}

Что такое интерфейс?

Имейте в виду, что одной из основных функций TypeScript является проверка типа переменной. как преждеconst port: number = 8080 и { response }: { response : any }Точно так же мы можем также проверить, имеет ли переменная тип объекта.

В TypeScript интерфейсы отвечают за присвоение имен типам.Определение ограничений типа в коде и вне егоэффективный метод.

Вот пример с интерфейсом:

// 写了个接口
interface LabeledValue {
  label: string;
}

// 此函数的labeledObj 参数是符合 LabeledValue 接口类型的
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}

let myObj = {label: "Size 10 Object"};
printLabel(myObj);

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

Теперь, когда знаний об интерфейсе достаточно, давайте вместе смоделируем некоторые поддельные данные (поскольку в этой статье не рассматривается работа с базой данных).

мы вstubs/todos.tsфайл для заполнения переменной todos некоторыми фиктивными данными. Это будет делать:

import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interface
import Todo from '../interfaces/Todo.ts';

let todos: Todo[] = [
  {
    id: v4.generate(),
    todo: 'walk dog',
    isCompleted: true,
  },
  {
    id: v4.generate(),
    todo: 'eat food',
    isCompleted: false,
  },
];

export default todos;
  • Следует отметить две вещи: здесь мы ссылаемся на новый модуль и передаемimport { v4 } from "https://deno.land/std/uuid/mod.ts";утверждение разрушаетv4Переменная. Далее мы используемv4.generate()Операторы могут генерировать случайную строку идентификатора. этоidне может бытьnumberтип, но должен бытьstringтип, так как наш предыдущийTodoИнтерфейс был объявленidТип должен быть строкой.
  • Еще одна вещь, которую следует отметить, это то, чтоlet todos: Todo[] = []утверждение. Этот оператор сообщает Deno, что наша переменная todos являетсяTodoМассив (в этот момент компилятор будет знать, что каждый элемент массива{id: _string_, todo: _string_ & isCompleted: _boolean_}тип, и никакие другие типы не допускаются).

Если вы хотите узнать больше информации, вы можете проверить:Интерфейсы Официальная документация.

Здорово, что вы зашли так далеко, продолжайте в том же духе.

Дуэйн Джонсон ценит всю вашу тяжелую работу.

Сосредоточимся на контроллере

В твоем controllers/todo.tsВ файле:

export default {
  getAllTodos: () => {},
  createTodo: async () => {},
  getTodoById: () => {},
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

Давайте сначала напишемgetAllTodosКонтроллер:

// stubs
import todos from "../stubs/todos.ts";

export default {
  /**
   * @description 获取所有 todos
   * @route GET /todos
   */
  getAllTodos: ({ response }: { response: any }) => {
    response.status = 200;
    response.body = {
      success: true,
      data: todos,
    };
  },
  createTodo: async () => {},
  getTodoById: () => {},
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

Прежде чем я начну представлять этот код, позвольте мне объяснить параметры, которые есть у каждого контроллера:context(контекстный) параметр.

Таким образом, мы можем деконструироватьgetAllTodos: (context) => {} для:

getAllTodos: ({ request, response, params }) => {}

и с тех пор, как мы используемtypescriptПосле этого нам нужно добавить объявление типа для каждой такой переменной:

getAllTodos: (
  { request, response, params }: { 
    request: any, 
    response: any, 
    params: { id: string },
  },
) => {}

На этом этапе мы деконструируем три переменные{ request, response, params }Добавлено описание типа.

  • requestПеременные, связанные с запросом, отправленным пользователем (например, заголовки запроса и тело запроса в формате JSON).
  • responseПеременные об информации, возвращаемой серверной стороной через API.
  • paramsПеременные — это параметры, которые мы определяем в конфигурации маршрутизации следующим образом:
.get("/todos/:id", ({ params}: { params: { id: string } }) => {})

/todos/:id середина :idэто переменная, используемая для получения динамических данных из URL-адреса. Поэтому, когда пользователь получает доступ к этому API (например,/todos/756)когда,756является:idзначение параметра. И мы знаем, что тип этого значения в URL-адресеstringКатегория.

Теперь, когда у нас есть основное объявление, давайте вернемся к нашему контроллеру todos:

// stubs
import todos from "../stubs/todos.ts";

export default {
  /**
   * @description 获取所有 todos
   * @route GET /todos
   */
  getAllTodos: ({ response }: { response: any }) => {
    response.status = 200;
    response.body = {
      success: true,
      data: todos,
    };
  },
  createTodo: async () => {},
  getTodoById: () => {},
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

заgetAllTodosЧто касается методов, нам нужно просто вернуть результат. Если вы помните, что я сказал раньше, вы помнитеresponseОн используется для обработки данных, которые сервер хочет вернуть пользователю.

Для тех, кто писал Node и Express, большая разница в том, что нам не нужноreturnОбъект ответа. Deno делает это автоматически.

Первое, что нам нужно сделать, это пройтиresponse.statusЧтобы установить код ответа для этого запроса,200.

Посмотреть больше кодов ответов HTTPДокументация по кодам состояния ответа HTTP на MDN.

Другое дело установитьresponse.bodyЗначение:

{
  success: true,
  data: todos
}

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

$ deno run --allow-net server.ts

Редакция: Атрибут --allow-net сообщает Deno, что это приложение предоставляет пользователю разрешение на доступ к сети через открытый порт.

Как только ваш пример на стороне сервера заработает, он вполне сносный.GET /todosспособ запросить этот API. Здесь я использую плагин для браузера Google Chrome.postman,Скачать здесь.

Вы можете использовать любой клиент в стиле REST, мне нравится использоватьpostmanПотому что он действительно прост в использовании.

В Postman откройте новую вкладку. Установите метод запроса наGETзапрос и вURLВведите в поле вводаhttp://localhost:8080/todos. нажмитеSendкнопку, чтобы получить желаемый результат:

GET /todos API возвращает результат.

прохладно! Один API готов, осталось 4 ждать нас 👍👍.

Если вы где-то застряли, вы можетеПоддержка репозитория исходного коданайти ответ.

Давайте сосредоточимся на следующем контроллере:

import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () => {},
  /**
   * @description Add a new todo
   * @route POST /todos
   */
  createTodo: async (
    { request, response }: { request: any; response: any },
  ) => {
    const body = await request.body();
    if (!request.hasBody) {
      response.status = 400;
      response.body = {
        success: false,
        message: "No data provided",
      };
      return;
    }

    // 如果请求体验证通过,则返回新增后的所有 todos
    let newTodo: Todo = {
      id: v4.generate(),
      todo: body.value.todo,
      isCompleted: false,
    };
    let data = [...todos, newTodo];
    response.body = {
      success: true,
      data,
    };
  },
  getTodoById: () => {},
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

Поскольку мы собираемся добавить в список новый Todo, я импортировал 2 общих модуля в файл контроллера:

  • import { v4 } from "https://deno.land/std/uuid/mod.ts"оператор используется для создания уникального идентификатора для каждого элемента todo.
  • import Todo from "../interfaces/Todo.ts";Оператор используется для обеспечения того, чтобы вновь созданная задача соответствовала стандарту формата интерфейса элемента задачи.

нашcreateTodoконтроллерasyncАсинхронные функции делегата используют некоторые методы Promise.

Давайте урежем небольшой фрагмент, иллюстрирующий это:

const body = await request.body();
if (!request.hasBody) {
  response.status = 400;
  response.body = {
    success: false,
    message: "No data provided",
  };
  return;
}

Сначала мы читаем содержимое JSON от пользователя в теле запроса. Далее мы используемoakвстроенныйrequest.hasBodyметод, чтобы проверить, является ли содержимое, переданное пользователем, пустым. Если пусто, мы введемif (!request.hasBody) {}Соответствующая операция выполняется в кодовом блоке.

Внутри мы устанавливаем код состояния тела ответа на400(400 означает, что у пользователя есть некоторые ошибки, которые не должны были произойти), и тело ответа, возвращаемое сервером,{success: false, message: "no data provided }. Затем программа выполняется непосредственноreturn;заявление, чтобы гарантировать, что следующий код не будет выполнен.

Далее пишем это:

// 如果请求体验证通过,则返回新增后的所有 todos
let newTodo: Todo = {
  id: v4.generate(),
  todo: body.value.todo,
  isCompleted: false,
};
let data = [...todos, newTodo];
response.body = {
  success: true,
  data,
};

где мы создаем новый элемент todo со следующим кодом:

let newTodo: Todo = {
  id: v4.generate(),
  todo: body.value.todo,
  isCompleted: false,
};

let newTodo: Todo = {}гарантияnewTodoЗначения переменных имеют тот же формат интерфейса, что и другие элементы todo. Затем мы используемv4.generate()Назначьте случайный идентификатор и установите ключ todo наbody.value.todoи воляisCompletedЗначение переменной установлено вfalse.

Здесь нам нужно знать, что мы можем передавать контент, который нам присылают пользователи.oak середина body.valueПриходите получить его.

Далее делаем так:

let data = [...todos, newTodo];
response.body = {
  success: true,
  data,
};

здесь будетnewTodoдобавляется ко всему списку дел и возвращается в тексте ответа{success: true & data: data.

На данный момент контроллер также успешно работает ✅.

Давайте перезапустим наш сервер:

$ deno run --allow-net server.ts

В почтальоне снова открываю новую вкладку. Способ установки запросаPOSTтипа и вURLВведите в поле вводаhttp://localhost:8080/todos, нажмитеSendВы получите следующий результат:

Поскольку вышеприведенное тело запроса отправило пустое содержимое, я получил код ответа об ошибке 400 и причину ошибки.

Но если мы добавим следующее содержимое JSON в тело запроса и повторно отправим:

При успешном результате POST /todos with { todo: "eat a lamma" } мы видим, что новый элемент был добавлен в список.

Круто, я вижу, что наши API работают ожидаемым образом.

Два API готовы, осталось сделать три.

Мы почти закончили, потому что большая часть сложного уже пройдена. ☺️ 🙂🤗🤩

Давайте посмотрим на третий API:

import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () => {},
  createTodo: async () => {},
  /**
   * @description 通过 ID 获取 todo
   * @route GET todos/:id
   */
  getTodoById: (
    { params, response }: { params: { id: string }; response: any },
  ) => {
    const todo: Todo | undefined = todos.find((t) => {
      return t.id === params.id;
    });
    if (!todo) {
      response.status = 404;
      response.body = {
        success: false,
        message: "No todo found",
      };
      return;
    }

    // 如果 todo 找到了
    response.status = 200;
    response.body = {
      success: true,
      data: todo,
    };
  },
  updateTodoById: async () => {},
  deleteTodoById: () => {},
};

Давай сначала поговоримGET todos/:idКонтроллер под , который ищет соответствующий элемент задачи по идентификатору.

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

const todo: Todo | undefined = todos.find((t) => t.id === params.id);
if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}

В первой строке мы объявляемconst todoпеременная и установить ее тип наTodo или undefined Добрый. следовательноtodoЭлементы могут быть толькоTodoПеременная спецификации интерфейса илиundefinedзначение, а не любой другой тип.

Далее мы используемtodos.find((t) => t.id === params.id);заявление для прохожденияArray.find()Методы иparams.idзначение, чтобы найти указанноеtodoэлемент. Если найдем, получимTodo Типtodoэлемент, отправьте, иначе получитеundefinedценность.

если вы получитеtodoЗначение не определено, что означает, что код в следующем условии if будет выполнен:

if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}

Здесь мы устанавливаем код состояния ответа как404,представляет собойnot foundСоответствующий элемент не найден, и формат тела возврата также стандартный{ status, message }.

Круто не правда ли? 😄

Далее мы просто пишем:

// 如果 todo 找到了
response.status = 200;
response.body = {
  success: true,
  data: todo,
};

Установите код состояния ответа на200Тело ответа и возвращаетsuccess: true & data: todo содержание.

Проверим в почтальоне:

Сначала перезагрузите сервер вместе:

$ deno run --allow-net server.ts

В почтальоне продолжайте открывать новую вкладку и установите метод запроса наGETзапрос и вURLВведите в поле вводаhttp://localhost:8080/todos/:id, нажмитеSendчтобы выполнить запрос.

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

Входим так:

Сервер возвращает 404 и сообщает нам, что соответствующие данные не найдены.

Но если вы введете правильный идентификатор, сервер вернет те же данные с идентификатором и этим идентификатором, а статус ответа будет 200.

Если вам нужно обратиться к исходному коду этой статьи, вы можете посетить здесь:@adeelibr/deno-playground.

Да, 3 API сделаны, осталось только 2.

import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () => {},
  createTodo: async () => {},
  getTodoById: () => {},
  /**
   * @description Update todo by id
   * @route PUT todos/:id
   */
  updateTodoById: async (
    { params, request, response }: {
      params: { id: string },
      request: any,
      response: any,
    },
  ) => {
    const todo: Todo | undefined = todos.find((t) => t.id === params.id);
    if (!todo) {
      response.status = 404;
      response.body = {
        success: false,
        message: "No todo found",
      };
      return;
    }

    // 如果找到相应 todo 则更新它
    const body = await request.body();
    const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
    let newTodos = todos.map((t) => {
      return t.id === params.id ? { ...t, ...updatedData } : t;
    });
    response.status = 200;
    response.body = {
      success: true,
      data: newTodos,
    };
  },
  deleteTodoById: () => {},
};

Давайте изучим следующий контроллерPUT todos/:id. Этот контроллер обновляет содержимое элемента.

Давайте продолжим усекать код, чтобы рассмотреть его поближе:

const todo: Todo | undefined = todos.find((t) => t.id === params.id);
if (!todo) {
  response.status = 404;
  response.body = {
    success: false,
    message: "No todo found",
  };
  return;
}

Это делается так же, как контроллер делал раньше, поэтому я не буду вдаваться в подробности.

Расширенный совет: если вы хотите сделать этот код общим блоком и использовать его в обоих контроллерах, это нормально.

Далее делаем так:

// 如果找到相应 todo 则更新它
const body = await request.body();
const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
let newTodos = todos.map((t) => {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});
response.status = 200;
response.body = {
  success: true,
  data: newTodos,
};

Код, на котором я хочу сосредоточиться, выглядит следующим образом:

const updatedData: { todo?: string; isCompleted?: boolean } = body.value;
let newTodos = todos.map((t) => {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});

Сначала мы выполняемconst updatedData = body.value, затем добавьте проверку типов вupdatedData, следующим образом:

updatedData: { todo?: string; isCompleted?: boolean }

Этот небольшой фрагмент кода сообщает TS:updatedDataПеременная — это знакомый объект, который может содержать или не содержать todo, isComplete.

Затем мы перебираем каждый элемент todo следующим образом:

let newTodos = todos.map((t) => {
  return t.id === params.id ? { ...t, ...updatedData } : t;
});

из которых когдаparams.id и t.idКогда значение одинаковое, мы переписываем содержимое объекта в это время с содержимым, которое пользователь хочет изменить.

Мы также успешно написали этот API.

Перезапускаем сервер:

$ deno run --allow-net server.ts

Откройте вкладку в Postman. Установите метод запроса наPUT, И вURLВведите в поле вводаhttp://localhost:8080/todos/:id, нажмитеSend:

Поскольку мы использовали генератор случайных идентификаторов, сначала нам нужно вызвать API, чтобы получить все элементы. И выберите идентификатор в списке элементов, чтобы протестировать новый API.

При каждом перезапуске программы Deno будет создаваться новый идентификатор.

Приведенное выше возвращает код состояния 404 и сообщает нам, что соответствующий элемент todo не найден.

Укажите известный идентификатор и заполните содержимое, которое необходимо изменить, в теле запроса. Сервер вернет измененный элемент и все остальные элементы.

Круто, четыре API готовы, осталось сделать только последний.

import { v4 } from "https://deno.land/std/uuid/mod.ts";
// interfaces
import Todo from "../interfaces/Todo.ts";
// stubs
import todos from "../stubs/todos.ts";

export default {
  getAllTodos: () => {},
  createTodo: async () => {},
  getTodoById: () => {},
  updateTodoById: async () => {},
  /**
   * @description 通过 ID 删除指定 todo
   * @route DELETE todos/:id
   */
  deleteTodoById: (
    { params, response }: { params: { id: string }; response: any },
  ) => {
    const allTodos = todos.filter((t) => t.id !== params.id);

    // remove the todo w.r.t id and return
    // remaining todos
    response.status = 200;
    response.body = {
      success: true,
      data: allTodos,
    };
  },
};

давайте обсудим в концеDelete todos/:idВыполнение контроллера, который удаляет соответствующий элемент todo с заданным идентификатором.

Нам просто нужно просто добавить метод фильтра здесь:

const allTodos = todos.filter((t) => t.id !== params.id);

перебрать все элементы и удалитьtodo.idи params.idэлементы с тем же значением и возвращают все остальные элементы.

Далее пишем это:

// 删除这个 todo 并返回其它所有内容
response.status = 200;
response.body = {
  success: true,
  data: allTodos,
};

Просто верните все списки задач, которые не имеют одного и того же todo.id.

Перезапускаем сервер:

$ deno run --allow-net server.ts

Откройте вкладку в Postman. Установите метод запроса наPUT, И вURLВведите в поле вводаhttp://localhost:8080/todos/:id, нажмитеSend:

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

При каждом перезапуске программы Deno будет создаваться новый идентификатор.

Наконец-то мы сделали все 5 API.

Теперь у нас осталось только две вещи:

  • Добавьте промежуточное ПО 404, чтобы пользователи могли получать подсказки при доступе к несуществующим маршрутам;
  • Добавьте API ведения журнала, чтобы печатать время выполнения всех запросов.

Создайте промежуточное ПО маршрутизации 404

В корневом каталоге проекта создайте файл с именемmiddlewares, и создайте папку с именемnotFound.tsПосле файла добавьте следующий код:

export default ({ response }: { response: any }) => {
  response.status = 404;
  response.body = {
    success: false,
    message: "404 - Not found.",
  };
};

Приведенный выше код не вводит ничего нового — он использует знакомый стиль для нашей структуры контроллера. вернулся только сюда404Код состояния (указывающий, что соответствующий маршрут не найден) и возвращенный фрагмент содержимого JSON:{ success, message }.

следующий в вашемserver.tsДобавьте в файл следующее:

  • Добавьте соответствующий оператор импорта вверху файла:
// 没有找到
import notFound from './middlewares/notFound.ts';
  • следующий вapp.use(todoRouter.allowedMethods())Добавьте следующий контент ниже:
app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

// 404 page
app.use(notFound);

Здесь важен порядок выполнения: всякий раз, когда мы пытаемся получить доступ к маршруту API, он сначала сопоставляется/проверяется изtodoRouterмаршрут. Если не найден, он будет выполненapp.use(notFound);утверждение.

Посмотрим, успешно ли он работает.

Перезапустите сервер:

$ deno run --allow-net server.ts

Откройте вкладку в Postman. Установите метод запроса наPUT, И вURLВведите в поле вводаhttp://localhost:8080/todos/:id, нажмитеSend:

Итак, теперь у нас есть промежуточное ПО для маршрутизации, котороеapp.use(notFound);надетьserver.tsПосле других маршрутов в файле. Если маршрут запроса не существует, он выполнится и вернет404код состояния (значение не найдено) и просто вернуть ответное сообщение, как обычно, т.е.{success, message}.

Расширенные советы: мы связались{success, message}формат, возвращаемый при сбое запроса,{success, data}Это формат, возвращаемый пользователю при успешном выполнении запроса. Таким образом, мы можем даже связать его как объект и добавить в проект, чтобы обеспечить согласованность интерфейса и безопасную проверку типов.

Круто, теперь у нас есть одно из этих промежуточных программ — давайте добавим еще одно промежуточное программное обеспечение для печати логов в терминал.

Запомнить: Если вы застряли в некоторых местах, вы можете взглянуть на вспомогательный исходный код статьи:@adeelibr/deno-playground.

Промежуточное ПО для печати логов в терминале

В твоемmiddlewaresСоздайте новую папку вlogger.tsфайл и заполните его следующим:

import {
  green,
  cyan,
  white,
  bgRed,
} from "https://deno.land/std@0.53.0/fmt/colors.ts";

const X_RESPONSE_TIME: string = "X-Response-Time";

export default {
  logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) => {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(`${green(request.method)} ${cyan(request.url.pathname)}`);
    console.log(`${bgRed(white(String(responseTime)))}`);
  },
  responseTime: async (
    { response }: { response: any },
    next: Function,
  ) => {
    const start = Date.now();
    await next();
    const ms: number = Date.now() - start;
    response.headers.set(X_RESPONSE_TIME, `${ms}ms`)
  },
};

существует server.tsДобавьте в файл следующий код:

  • Добавьте оператор импорта вверху файла, чтобы импортировать модуль:
// logger
import logger from './middlewares/logger.ts';
  • упомянутый ранееtodoRouterДобавьте код промежуточного программного обеспечения, подобный этому, перед кодом:
// 以下代码的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

Теперь давайте обсудим, что именно произошло.

Давайте сначала обсудимlogger.tsфайл, сначала обрезать и посмотреть здесь:

import {
  green,
  cyan,
  white,
  bgRed,
} from "https://deno.land/std@0.53.0/fmt/colors.ts";

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

Вот и наши предыдущиеserver.tsиспользуется в файлеeventListenerподобным образом. Мы будем использовать цветные сообщения журнала для регистрации наших запросов API.

Далее мы устанавливаемconst X_RESPONSE_TIME: string = "X-Response-Time";. Этот оператор используется для вставки в заголовок заголовка ответа, когда приходит запрос пользователя.X_RESPONSE_TIMEЗначение переменной:X-Response-Time. Я объясню позже.

Затем мы экспортируем объект следующим образом:

export default {
  logger: async ({ response, request }, next) {}
  responseTime: async ({ response }, next) {}
};

В это время мыserver.tsиспользуется так:

// 以下两行的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);

Теперь давайте обсудим, что делает промежуточное ПО журнала, и перейдемnext()пояснить его выполнение.

На приведенном выше рисунке показана последовательность выполнения ПО промежуточного слоя ведения журнала при вызове API GET /todos.

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

Отсюда этот абзац:

export default {
  logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) => {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(${green(request.method)} ${cyan(request.url.pathname)});
    console.log(${bgRed(white(String(responseTime)))});
  },
  responseTime: async (
    { response }: { response: any },
    next: Function,
  ) => {
    const start = Date.now();
    await next();
    const ms: number = Date.now() - start;
    response.headers.set(X_RESPONSE_TIME, ${ms}ms)
  },
};

Обратите внимание, что мыserver.tsКак писать в:

// 以下代码的编写顺序很重要
app.use(logger.logger);
app.use(logger.responseTime);

app.use(todoRouter.routes());
app.use(todoRouter.allowedMethods());

Порядок выполнения здесь следующий:

  • промежуточное ПО logger.logger
  • ПО промежуточного слоя logger.responseTime
  • контроллер todoRouter (к какому бы маршруту пользователь ни хотел получить доступ, для пояснения предполагается, что пользователь вызываетGET /todosчтобы получить все задачи).

Таким образом, сначала будет выполнено содержимое logger.logger:

logger: async (
    { response, request }: { response: any, request: any },
    next: Function,
  ) => {
    await next();
    const responseTime = response.headers.get(X_RESPONSE_TIME);
    console.log(${green(request.method)} ${cyan(request.url.pathname)});
    console.log(${bgRed(white(String(responseTime)))});
  },

при встречеawait next()немедленно перейдет к следующему промежуточному программному обеспечению -responseTime начальство.

Поделитесь этим изображением еще раз, чтобы просмотреть процесс.

существует responseTime, сначала будут выполнены только следующие две строки (см. процесс выполнения 2 на рисунке выше):

const start = Date.now();
await next();

затем перейти кgetAllTodosконтроллер и выполнитьgetAllTodosвесь код в .

В этом контроллере нам не нужно использоватьnext(), он автоматически вернется кresponseTimeпромежуточное ПО и выполните следующее:

const ms: number = Date.now() - start;
response.headers.set(X_RESPONSE_TIME, ${ms}ms)

Теперь мы понимаем процесс порядка выполнения 2, 3, 4 (см. выше).

Вот конкретный процесс, который происходит:

  • мы выполняемconst start = Date.now();захватить сmsданные в единицах. Тогда мы сразу звонимnext()прыгать кgetAllTodosконтроллер и запустить код в нем. затем вернуться кresponseTimeв контроллере.
  • Затем, выполнивconst ms: number = Date.now() - start;чтобы вычесть время, когда запрос только что пришел. Здесь он вернет разность в миллисекундах, которая сообщит Deno все время, которое потребовалось для выполнения контроллера getAllTodos.

Поделитесь этим файлом еще раз, чтобы просмотреть процесс:

  • Далее мыresponseУстановите в заголовках заголовка ответа:
response.headers.set(X_RESPONSE_TIME, ${ms}ms)

Установите значение X-Response-Time на количество миллисекунд, потраченных API Deno getAllTodos.

  • Затем от последовательности выполнения 4 обратно к последовательности выполнения 5 (см. рисунок выше).

Просто напишите сюда:

const responseTime = response.headers.get(X_RESPONSE_TIME);
console.log(${green(request.method)} ${cyan(request.url.pathname)});
console.log(${bgRed(white(String(responseTime)))});
  • При печати журнала мы начинаем сX-Response-TimeПолучите время, необходимое для выполнения API.
  • Далее печатаем в терминале цветным шрифтом.

request.methodВозвращает метод, запрошенный пользователем, напримерGET, PUT 等,в то же время request.url.pathnameВозвращает путь, запрошенный пользователем, например/todos.

Посмотрим, успешно ли он работает.

Перезапустите сервер:

$ deno run --allow-net server.ts

Откройте вкладку в Postman. Установите метод запроса наGET, И вURLВведите в поле вводаhttp://localhost:8080/todos, нажмитеSend:

Сделайте еще несколько запросов к API в Postman, а затем вернитесь в консоль, чтобы просмотреть логи, и вы должны увидеть что-то похожее на следующее:

Каждый запрос API будет регистрироваться в терминале промежуточным программным обеспечением ведения журнала.

Вот и все - у нас все получилось.

Если вы где-то застряли, вы можете взглянуть на полный исходный код этой статьи:GitHub.com/Ди в чужом/…

Я надеюсь, что вы найдете эту статью полезной и действительно поможет вам узнать что-то новое.

Если вам это нравится, пожалуйста, поделитесь им на социальных платформах. Если вы хотите пообщаться глубже, вы можетеTwitterсвяжитесь со мной по .