В этом посте я хотел бы рассказать и показать процесс создания небольшого API с помощью Deno. Deno — это новейшая среда выполнения Javascript и Typescript, разработанная создателем Node.js Райаном Далем.
Наши цели :
- Разработать API, который будет работать с пользовательскими данными
- Предоставьте возможность использовать методы GET, POST, PUT и DELETE.
- Сохраняйте и обновляйте пользовательские данные в локальный файл JSON.
- Используйте фреймворк для ускорения разработки
Для этого примера я использовал версию Deno 0.22, и этот код может не работать в будущих версиях.
Версию установленного Deno можно узнать командой дено-версия в терминале.
Структура программы
Как вы, возможно, заметили, это похоже на небольшое веб-приложение на Node.js.handlers middlewares models services config.ts index.ts routing.ts
- обработчики - содержит обработчики маршрутов
- промежуточное ПО — содержит функции, которые будут запускаться при каждом запросе
- модели — содержит обозначение моделей, в нашем случае это только пользовательский интерфейс
- услуги — содержит. услуги!
- config.ts - файл конфигурации приложения
- index.ts — точка входа для нашего приложения
- маршрутизация.
ts - содержит маршруты API
Выбор фреймворка
Существует множество отличных фреймворков для Node.js. Один из самых популярных – Выражать .Существует также современная версия Экспресса — Коа .
К сожалению, Deno не поддерживает библиотеки Node.js и выбора гораздо меньше, но есть фреймворк для Deno, основанный на Koa — Дуб .
Мы будем использовать его для нашего небольшого проекта.
Если вы никогда не использовали Koa, не волнуйтесь, он во многом похож на Express.
Создание точки входа приложения
index.ts import { Application } from " https://deno.land/x/oak/mod.ts ";
import { APP_HOST, APP_PORT } from ".
/config.ts";
import router from ".
/routing.ts";
import notFound from ".
/handlers/notFound.ts";
import errorMiddleware from ".
/middlewares/error.ts";
const app = new Application();
app.use(errorMiddleware);
app.use(router.routes());
app.use(router.allowedMethods());
app.use(notFound);
console.log(`Listening on ${APP_PORT}.
`);
await app.listen(`${APP_HOST}:${APP_PORT}`);
В первой линии мы используем одну из основных возможностей Deno — импорт модулей напрямую из Интернета.
После этого нет ничего необычного: создаем приложение, добавляем промежуточное ПО, маршруты и, наконец, запускаем сервер.
Все то же самое, что и при использовании Express или Koa.
Создание конфигурации
config.ts const env = Deno.env();
export const APP_HOST = env.APP_HOST || "127.0.0.1";
export const APP_PORT = env.APP_PORT || 4000;
export const DB_PATH = env.DB_PATH || ".
/db/users.json";
Наш файл конфигурации.
Настройки переносятся из среды запуска, но мы также добавим значения по умолчанию.
Функция Дено.
env() является аналогом процесс.
env в Node.js.
Добавление модели пользователя
модели/user.ts export interface User {
id: string;
name: string;
role: string;
jiraAdmin: boolean;
added: Date;
}
Создание файла с маршрутами
маршрутизация.
ts import { Router } from " https://deno.land/x/oak/mod.ts ";
import getUsers from ".
/handlers/getUsers.ts"; import getUserDetails from ".
/handlers/getUserDetails.ts"; import createUser from ".
/handlers/createUser.ts"; import updateUser from ".
/handlers/updateUser.ts"; import deleteUser from ".
/handlers/deleteUser.ts"; const router = new Router(); router .
get("/users", getUsers) .
get("/users/:id", getUserDetails) .
post("/users", createUser) .
put("/users/:id", updateUser) .
delete("/users/:id", deleteUser);
export default router;
Опять же, ничего необычного.
Мы создали маршрутизатор и добавили к нему несколько маршрутов.
Похоже, вы скопировали код из приложения Express, не так ли?
Обработка событий для маршрутов
обработчики/getUsers.ts import { getUsers } from ".
/services/users.ts";
export default async ({ response }) => {
response.body = await getUsers();
};
Возвращает всех пользователей.
Если вы никогда не использовали Koa, позвольте мне объяснить.
Объект ответ является аналогом рез в Экспрессе.
Объект рез в Express есть несколько методов, таких как JSON или отправлять , которые используются для отправки ответа.
В Oak и Koa нам нужно установить значение, которое мы хотим вернуть свойству.
ответ.тело .
обработчики/getUserDetails.ts import { getUser } from ".
/services/users.ts";
export default async ({ params, response }) => {
const userId = params.id;
if (!userId) {
response.status = 400;
response.body = { msg: "Invalid user id" };
return;
}
const foundUser = await getUser(userId);
if (!foundUser) {
response.status = 404;
response.body = { msg: `User with ID ${userId} not found` };
return;
}
response.body = foundUser;
};
Здесь тоже все легко.
Обработчик возвращает пользователя с требуемым идентификатором.
обработчики/createUser.ts import { createUser } from ".
/services/users.ts";
export default async ({ request, response }) => {
if (!request.hasBody) {
response.status = 400;
response.body = { msg: "Invalid user data" };
return;
}
const {
value: { name, role, jiraAdmin }
} = await request.body();
if (!name || !role) {
response.status = 422;
response.body = { msg: "Incorrect user data. Name and role are required" };
return;
}
const userId = await createUser({ name, role, jiraAdmin });
response.body = { msg: "User created", userId };
};
Этот обработчик отвечает за создание пользователя.
обработчики/updateUser.ts import { updateUser } from ".
/services/users.ts";
export default async ({ params, request, response }) => {
const userId = params.id;
if (!userId) {
response.status = 400;
response.body = { msg: "Invalid user id" };
return;
}
if (!request.hasBody) {
response.status = 400;
response.body = { msg: "Invalid user data" };
return;
}
const {
value: { name, role, jiraAdmin }
} = await request.body();
await updateUser(userId, { name, role, jiraAdmin });
response.body = { msg: "User updated" };
};
Обработчик проверяет, существует ли пользователь с указанным идентификатором, и обновляет данные пользователя.
обработчики/deleteUser.ts import { deleteUser, getUser } from ".
/services/users.ts";
export default async ({ params, response }) => {
const userId = params.id;
if (!userId) {
response.status = 400;
response.body = { msg: "Invalid user id" };
return;
}
const foundUser = await getUser(userId);
if (!foundUser) {
response.status = 404;
response.body = { msg: `User with ID ${userId} not found` };
return;
}
await deleteUser(userId);
response.body = { msg: "User deleted" };
};
Ответственный за удаление пользователя.
Также желательно обрабатывать запросы по несуществующим маршрутам и возвращать сообщение об ошибке.
обработчики/notFound.ts export default ({ response }) => {
response.status = 404;
response.body = { msg: "Not Found" };
};
Добавление услуг
Прежде чем создавать сервисы, которые будут работать с пользовательскими данными, нам необходимо создать два небольших вспомогательных сервиса.
услуги/createId.ts import { v4 as uuid } from " https://deno.land/std/uuid/mod.ts ";
export default () => uuid.generate();
Каждый новый пользователь получит уникальный идентификатор.
Давайте воспользуемся модулем uuid из стандартной библиотеки Deno для генерации случайного числа.
услуги/db.ts import { DB_PATH } from ".
/config.ts"; import { User } from ".
/models/user.ts";
export const fetchData = async (): Promise<User[]> => {
const data = await Deno.readFile(DB_PATH);
const decoder = new TextDecoder();
const decodedData = decoder.decode(data);
return JSON.parse(decodedData);
};
export const persistData = async (data): Promise<void> => {
const encoder = new TextEncoder();
await Deno.writeFile(DB_PATH, encoder.encode(JSON.stringify(data)));
};
Этот сервис поможет взаимодействовать с JSON-файлом, в котором будут храниться данные пользователя.
Чтобы получить всех пользователей, прочитайте содержимое файла.
Функция readFile возвращает объект типа Uint8Array , который необходимо преобразовать в тип перед вводом в файл JSON. Нить .
И, наконец, основной сервис для работы с пользовательскими данными.
услуги/users.ts import { fetchData, persistData } from ".
/db.ts"; import { User } from ".
/models/user.ts"; import createId from ".
/services/createId.ts"; type UserData = Pick<User, "name" | "role" | "jiraAdmin">; export const getUsers = async (): Promise<User[]> => { const users = await fetchData(); // sort by name return users.sort((a, b) => a.name.localeCompare(b.name)); }; export const getUser = async (userId: string): Promise<User | undefined> => { const users = await fetchData(); return users.find(({ id }) => id === userId); }; export const createUser = async (userData: UserData): Promise<string> => { const users = await fetchData(); const newUser: User = { id: createId(), name: String(userData.name), role: String(userData.role), jiraAdmin: "jiraAdmin" in userData ? Boolean(userData.jiraAdmin) : false, added: new Date() }; await persistData([.
users, newUser]); return newUser.id; }; export const updateUser = async ( userId: string, userData: UserData ): Promise<void> => { const user = await getUser(userId); if (!user) { throw new Error("User not found"); } const updatedUser = { .
user, name: userData.name !== undefined ? String(userData.name) : user.name, role: userData.role !== undefined ? String(userData.role) : user.role, jiraAdmin: userData.jiraAdmin !== undefined ? Boolean(userData.jiraAdmin) : user.jiraAdmin }; const users = await fetchData(); const filteredUsers = users.filter(user => user.id !== userId); persistData([.
filteredUsers, updatedUser]);
};
export const deleteUser = async (userId: string): Promise<void> => {
const users = await getUsers();
const filteredUsers = users.filter(user => user.id !== userId);
persistData(filteredUsers);
};
Здесь много кода, но это чистый Typescript.
Обработка ошибок
Что может быть хуже того, что произойдет, если сервис, работающий с пользовательскими данными, допустит ошибку? Вся программа может выйти из строя.Чтобы избежать этого сценария, вы можете использовать конструкцию try/catch в каждом обработчике.
Но есть более элегантное решение — добавлять промежуточное программное обеспечение перед каждым маршрутом и предотвращать все возможные непредвиденные ошибки.
промежуточное программное обеспечение/error.ts export default async ({ response }, next) => {
try {
await next();
} catch (err) {
response.status = 500;
response.body = { msg: err.message };
}
};
Прежде чем запустить саму программу, нам необходимо добавить данные запуска.
БД/users.json [
{
"id": "1",
"name": "Daniel",
"role": "Software Architect",
"jiraAdmin": true,
"added": "2017-10-15"
},
{
"id": "2",
"name": "Markus",
"role": "Frontend Engineer",
"jiraAdmin": false,
"added": "2018-09-01"
}
]
Вот и все! Теперь мы можем попробовать запустить наше приложение: deno -A index.ts
Флаг «А» указывает на то, что программе не требуется предоставлять какие-либо особые разрешения.
Имейте в виду, что этот флаг небезопасно использовать в производстве.
Скорее всего, вы увидите множество строк с загрузкой (Download) и компиляцией (Compile).
В итоге должна появиться заветная строка: Listening on 4000
Давайте подведем итоги
Какие инструменты мы использовали?- Глобальный объект Дено для чтения/записи файлов
- uuid из стандартной библиотеки Deno для создания уникального идентификатора
- дуб — сторонний фреймворк, вдохновленный Koa для Node.js.
- Объекты Pure Typescript, TextEncode или JSON, включенные в Javascript
В чем отличие от Node.js?
- Нет необходимости устанавливать и настраивать компилятор Typescript или другие инструменты, такие как ts-node. Вы можете просто запустить программу командой дено index.ts
- Включайте все сторонние модули непосредственно в код без необходимости предварительной установки.
- Отсутствуют package.json и package-lock.json.
- Отсутствие node_modules в корневом каталоге нашей программы.
Все скачанные файлы находятся в глобальном кеше.
Теги: #api #JavaScript #node.js #typescript #Deno
-
Перешеек
19 Oct, 24 -
Кликхаус. Расширение Кластера
19 Oct, 24 -
Шпаргалка По Созданию Ит-Бюджета
19 Oct, 24 -
Counter-Strike 1.6 Доступен Для Linux
19 Oct, 24