DevGang
Авторизоваться

Создание REST API с помощью Deno и Hono.js: пошаговое руководство

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

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

  • hono — веб-фреймворк
  • denodb — объектно-реляционный преобразователь (ORM)
  • zod — проверка схемы

Прежде чем приступить к этой статье, я рекомендую вам установить 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, указанном ниже.

Github Repo

Источник:

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться