Membuat API Deno kecil

Dalam posting ini saya ingin memberi tahu dan menunjukkan proses pembuatan API kecil menggunakan Deno. Deno adalah peluncur Javascript dan Typcript terbaru yang dikembangkan oleh pencipta Node.js Ryan Dahl.



Tujuan kami :

  • Kembangkan API yang akan bekerja dengan data pengguna
  • Berikan kemampuan untuk menggunakan metode GET, POST, PUT, dan DELETE
  • Simpan dan perbarui data pengguna dalam file JSON lokal
  • Gunakan kerangka kerja untuk mempercepat pengembangan

Satu-satunya hal yang perlu kita instal adalah Deno. Deno mendukung naskah tepat di luar kotak. Untuk contoh ini, saya menggunakan Deno versi 0.22 dan kode ini mungkin tidak berfungsi pada versi yang akan datang.
Versi Deno yang terinstal dapat ditemukan dengan perintah versi deno di terminal.

Struktur program


handlers
middlewares
models
services
config.ts
index.ts
routing.ts

Seperti yang Anda lihat, sepertinya aplikasi web kecil di Node.js.

  • penangan - berisi penangan rute
  • middlewares - berisi fungsi yang akan diluncurkan pada setiap permintaan
  • model - berisi penunjukan model, dalam kasus kami ini hanya antarmuka pengguna
  • layanan - berisi ... layanan!
  • config.ts - file konfigurasi aplikasi
  • index.ts adalah titik masuk untuk aplikasi kita
  • routing.ts - berisi rute API

Temukan kerangka kerja


Ada banyak kerangka kerja hebat untuk Node.js. Salah satu yang paling populer adalah Express . Ada juga versi modern dari Express'a - Koa . Sayangnya, Deno tidak mendukung perpustakaan Node.js dan pilihannya jauh lebih kecil, tetapi ada kerangka kerja untuk Deno, yang didasarkan pada Koa - Oak . Kami akan menggunakannya untuk proyek kecil kami. Jika Anda tidak pernah menggunakan Koa, jangan khawatir, ini sangat mirip dengan Express.

Membuat titik masuk aplikasi


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}`);

Pada baris pertama, kami menggunakan salah satu chip Deno utama - mengimpor modul langsung dari Internet. Setelah itu, tidak ada yang aneh: kami membuat aplikasi, menambahkan middleware, rute dan, akhirnya, memulai server. Semuanya sama seperti ketika menggunakan Express atau Koa.

Buat konfigurasi


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";

File konfigurasi kami. Pengaturan ditransfer dari lingkungan peluncuran, tetapi kami juga akan menambahkan nilai default. Fungsi Deno.env () adalah analog dari process.env di Node.js.

Menambahkan model pengguna


model / user.ts

export interface User {
  id: string;
  name: string;
  role: string;
  jiraAdmin: boolean;
  added: Date;
}

Membuat File Rute


routing.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;

Sekali lagi, tidak ada yang aneh. Kami membuat router dan menambahkan beberapa rute ke sana. Sepertinya Anda menyalin kode dari aplikasi Express, bukan?

Penanganan acara untuk rute


handler / getUsers.ts

import { getUsers } from "../services/users.ts";

export default async ({ response }) => {
  response.body = await getUsers();
};

Mengembalikan semua pengguna. Jika Anda belum pernah menggunakan Koa, maka saya akan menjelaskan. Objek respons analog dengan res di Express. Objek res di Express memiliki beberapa metode seperti json atau send , yang digunakan untuk mengirim respons. Di Oak dan Koa, kita perlu menetapkan nilai yang ingin kita kembalikan ke properti response.body .

handler / 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;
};

Semuanya juga mudah di sini. Pawang mengembalikan pengguna dengan Id yang diinginkan.

handler / 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 };
};

Pawang ini bertanggung jawab untuk menciptakan pengguna.

handler / 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" };
};

Pawang memeriksa apakah pengguna dengan ID yang ditentukan ada dan memperbarui data pengguna.

handler / 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" };
};

Bertanggung jawab untuk menghapus pengguna.

Dianjurkan juga untuk memproses permintaan untuk rute yang tidak ada dan mengembalikan pesan kesalahan.

handler / notFound.ts

export default ({ response }) => {
  response.status = 404;
  response.body = { msg: "Not Found" };
};

Menambahkan Layanan


Sebelum membuat layanan yang akan bekerja dengan data pengguna, kita perlu membuat dua layanan tambahan kecil.

layanan / createId.ts

import { v4 as uuid } from "https://deno.land/std/uuid/mod.ts";

export default () => uuid.generate();

Setiap pengguna baru akan menerima id unik. Kami akan menggunakan modul uuid dari perpustakaan standar Deno untuk menghasilkan angka acak.

layanan / 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)));
};

Layanan ini akan membantu berinteraksi dengan file JSON di mana data pengguna akan disimpan.
Untuk mendapatkan semua pengguna, baca konten file. Fungsi readFile mengembalikan objek bertipe Uint8Array , yang harus dikonversi ke tipe String sebelum memasukkan ke file JSON .

Dan akhirnya, layanan utama untuk bekerja dengan data pengguna.

layanan / 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);
};

Ada banyak kode di sini, tetapi ini adalah naskah murni.

Galat saat memproses


Apa yang bisa lebih buruk dari apa yang akan terjadi jika terjadi kesalahan layanan yang bekerja dengan data pengguna? Seluruh program mungkin macet. Untuk menghindari skenario ini, Anda dapat menggunakan konstruksi coba / tangkap di setiap penangan. Tetapi ada solusi yang lebih elegan - tambahkan middleware di depan setiap rute dan cegah kesalahan tak terduga yang mungkin terjadi.

middlewares / error.ts

export default async ({ response }, next) => {
  try {
    await next();
  } catch (err) {
    response.status = 500;
    response.body = { msg: err.message };
  }
};

Sebelum memulai program itu sendiri, kita perlu menambahkan data untuk dijalankan.

db / 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"
  }
]

Itu saja! Sekarang Anda dapat mencoba menjalankan aplikasi kami:

deno -A index.ts

Bendera "A" berarti bahwa program tidak perlu diberikan izin terpisah. Jangan lupa bahwa menggunakan flag ini tidak aman dalam produksi.

Kemungkinan besar, Anda akan melihat banyak baris dengan unduhan (Unduh) dan kompilasi (Kompilasi). Pada akhirnya, garis yang didambakan akan muncul:

Listening on 4000

Untuk meringkas


Alat apa yang kami gunakan?

  1. Deno objek global untuk membaca / menulis file
  2. uuid dari perpustakaan standar Deno untuk membuat id unik
  3. oak - kerangka kerja pihak ketiga yang diinspirasi oleh Koa untuk Node.js
  4. Objek Teks Murni, TextEncode, atau JSON termasuk dalam Javascript

Apa perbedaan dari Node.js?


  • Tidak perlu menginstal dan mengonfigurasi kompiler untuk Typecript atau alat lain seperti ts-node. Anda cukup menjalankan program dengan perintah deno index.ts
  • Dimasukkannya semua modul pihak ketiga langsung dalam kode tanpa perlu instalasi awal
  • Kurangnya package.json dan package-lock.json
  • Tidak adanya node_modules di direktori root program kami. Semua file yang diunduh terletak di cache global

Jika perlu, kode sumber dapat ditemukan di sini .

Source: https://habr.com/ru/post/undefined/


All Articles