Создание REST API с помощью Deno и Hono.js: пошаговое руководство
В этой статье мы собираемся создать REST API, в котором мы выполняем знаменитый CRUD, и чтобы у каждого была возможность протестировать локально, будет использоваться база данных SQLite.
Чтобы дать вам немного больше контекста, в этой статье мы собираемся использовать следующие технологии:
Прежде чем приступить к этой статье, я рекомендую вам установить Deno и иметь небольшой опыт использования Node.
Настройка проекта
Чтобы начать, перейдите в каталог по вашему выбору и выполните следующую команду:
deno init .
Ожидается, что приведенная выше команда создаст набор файлов в рабочей области, этим мы инициализировали проект Deno и собираемся внести некоторые изменения в файл deno.jsonc
.
Начнем с определения некоторых команд, которые мы собираемся запускать с помощью средства запускаdeno tasks
:
{
"tasks": {
"dev": "deno run --watch main.ts",
"build": "deno compile main.ts"
}
}
Далее определим некоторые зависимости, которые необходимо импортировать в проект:
{
// ...
"imports": {
"hono": "https://deno.land/x/hono@v3.2.6/mod.ts",
"hono/middleware": "https://deno.land/x/hono@v3.2.6/middleware.ts",
"server": "https://deno.land/std@0.192.0/http/server.ts",
"denodb": "https://deno.land/x/denodb@v1.0.40/mod.ts",
"zod": "https://deno.land/x/zod@v3.21.4/mod.ts"
}
}
С этим последним изменением в deno.jsonc мы можем сделать конфигурацию проекта завершенной, однако, если вы хотите узнать больше о предмете и расширить конфигурацию проекта, вы можете посмотреть здесь.
Но сейчас самое время запачкать руки!
Создание схемы базы данных и клиента
Следующим шагом будет создание сущностей, присутствующих в проекте, в этом случае у нас будет только одна, которая будет Книгой, но в дополнение к сущности нам также потребуется проверка схемы.
// @/db/models/book.ts
import { DataTypes, Model } from "denodb";
import { z } from "zod";
export class Book extends Model {
static table = "books";
static timestamps = true;
static fields = {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
title: {
type: DataTypes.STRING,
allowNull: false,
length: 25,
},
description: {
type: DataTypes.STRING,
allowNull: false,
length: 100,
},
isAvailable: {
type: DataTypes.BOOLEAN,
allowNull: false,
},
};
static defaults = {
isAvailable: true,
};
}
export const bookSchema = z.object({
title: z.string(),
description: z.string(),
isAvailable: z.boolean(),
});
Теперь, когда объект создан, нам нужно создать подключение к базе данных, а затем синхронизировать его. Для этого сначала нам нужно будет импортировать объект Book, который мы только что создали, затем мы создадим коннектор и клиентский экземпляр базы данных, и из этого мы будем синхронизироваться с базой данных.
// @/db/connect.ts
import { Database, SQLite3Connector } from "denodb";
import { Book } from "./models/book.ts";
const connector = new SQLite3Connector({
filepath: "./dev.sqlite",
});
export const db = new Database(connector);
db.link([Book]);
Таким образом, мы уже создали схему и клиент базы данных и можем перейти к следующему шагу.
Определить маршруты
Принимая во внимание то, что было создано в последних пунктах, теперь мы можем перейти к определению маршрутов API. Сначала нам нужно импортировать Hono, чтобы создать маршрутизатор, и нам нужно импортировать сущность Book
и схему bookSchema
.
// @/router/book.ts
import { Hono } from "hono";
import { Book, bookSchema } from "../db/models/book.ts";
const book = new Hono();
// routes come here...
export { book };
Сделав это, мы теперь можем определить первый маршрут, по которому мы получим все книги, хранящиеся в базе данных.
book.get("/book", async (c) => {
const list = await Book.all();
return c.json({ list }, 200);
});
В следующем маршруте в конечной точке мы определим параметр запроса id
для получения конкретной книги с учетом ее уникального идентификатора.
book.get("/book/:id", async (c) => {
const { id } = c.req.param();
const book = await Book.where("id", id).first();
return c.json(book, 200);
});
В настоящее время у нас определены два маршрута: один для получения всех книг, а другой — для получения конкретной книги. Но в настоящее время база данных пуста, и нам нужно создать маршрут, отвечающий за вставку новой книги в базу данных.
С помощью этого маршрута вы должны определить json с данными книги в теле запроса, чтобы его можно было вставить, и чтобы гарантировать, что мы вставляем ожидаемые данные, мы собираемся использовать bookSchema
.
book.post("/book", async (c) => {
const body = await c.req.json();
const val = bookSchema.safeParse(body);
if (!val.success) return c.text("Invalid!", 500);
await Book.create({ ...val.data });
return c.body("Created", 201);
});
После этого, поскольку нам удалось создать новую книгу, мы также должны иметь возможность ее обновить. Для этого нам нужно будет определить параметр запроса id в маршруте, чтобы мы знали, какую книгу мы хотим обновить, затем в теле запроса ожидается, что данные будут такими, как ожидалось.
book.put("/book/:id", async (c) => {
const { id } = c.req.param();
const body = await c.req.json();
const val = bookSchema.safeParse(body);
if (!val.success) return c.text("Invalid!", 500);
await Book.where("id", id).update({ ...val.data });
return c.body("Updated", 200);
});
В последнюю очередь осталось реализовать маршрут, отвечающий за удаление книги с учетом значения параметра запроса id
.
book.delete("/book/:id", async (c) => {
const { id } = c.req.param();
await Book.deleteById(id);
return c.body("Deleted", 200);
});
Теперь мы можем сказать, что у нас прописан каждый из маршрутов, что позволяет нам перейти к следующему и последнему шагу.
Настройка промежуточного программного обеспечения
На этом шаге мы собираемся импортировать необходимое промежуточное программное обеспечение для приложения, не забывая о маршрутизаторе, который только что был создан, а также инициализируем синхронизацию базы данных и обслуживаем API. Так:
// @/main.ts
import { Hono } from "hono";
import { cors, logger, prettyJSON } from "hono/middleware";
import { serve } from "server";
import { book } from "./router/book.ts";
import { db } from "./db/connect.ts";
const api = new Hono();
api.use("*", logger());
api.use("*", prettyJSON());
api.use("/api/*", cors());
api.route("/api", book);
api.notFound((c) => c.json({ message: "Not Found" }, 404));
await db.sync();
serve(api.fetch);
Чтобы запустить процесс, просто запустите эту команду:
deno task dev
Чтобы собрать проект, просто запустите эту команду:
deno task build
Заключение
Я надеюсь, что вы нашли эту статью полезной, независимо от того, используете ли вы информацию в существующем проекте или просто хотите попробовать.
Пожалуйста, дайте мне знать, если вы заметили какие-либо ошибки в статье, оставив комментарий. И, если вы хотите увидеть исходный код этой статьи, вы можете найти его в репозитории github, указанном ниже.