- Оригинальный адрес:How to Create a Todo API in Deno and Oak
- Оригинальный автор: Адиль Имран
- Исходное время выпуска: 2020-05-29
- Переводчик:hylerrix
- Примечание. Эта статья следует заСпецификация перевода freeCodeCamp, и эта статья будет включена в«Искусство дено исследований»в переводе.
- Примечания: Официальный сайт электронной книги "The Art of Deno Research" открыт!deno-tutorial.js.org
Преамбула
Я разработчик 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
адрес, вы можете получить следующие результаты:
Самая сложная часть почти сделана, но мы только на 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свяжитесь со мной по .