Skip to content

API Rest: Fang JS + Drizzle ORM

¿Quieres ver de lo que es capaz Fang JS cuando se ensucia las manos? En este ejemplo, construiremos una API Rest básica, que nos permita hacer un CRUD de productos, para ello usaremos la arquitectura recomendada. En este ejemplo veremos:

  • Persistencia: Drizzle ORM sobre una base de datos PostgreSQL/SQLite.
  • Validación: Esquemas de Zod importados desde @fang-js/fang/zod.
  • Arquitectura: Separación clara entre Controladores y Servicios.

Antes de empezar, asegúrate de tener instalado Drizzle en tu proyecto:

Terminal window
npm i drizzle-orm @libsql/client dotenv
npm i -D drizzle-kit tsx

A continuación, se muestra la implementación de la API. Observa cómo el Context fluye a través de los servicios y cómo se usan los métodos del context y las excepciones.

├── 📁 src
│ ├── 📁 config
│ │ └── 📄 keys.ts
│ ├── 📁 database
│ │ └── 📄 schema.ts
│ ├── 📁 lib
│ │ └── 📄 drizzle.ts
│ ├── 📁 product
│ │ ├── 📄 product-controller.ts
│ │ ├── 📄 product-scheme.ts
│ │ └── 📄 product-service.ts
│ └── 📄 index.ts
├── 📄 dev.db
├── 📄 drizzle.config.ts
├── ⚙️ package-lock.json
├── ⚙️ package.json
└── ⚙️ tsconfig.json
import { Fang } from "@fang-js/fang";
import { ProductController } from "./product/product-controller.js";
import { NotFoundException } from "@fang-js/fang/exceptions";
import { cors } from "@fang-js/fang/middlewares";
import { methods } from "@fang-js/fang/types";
const app = new Fang();
app.use(
cors({
origin: "*",
methods: [methods.GET, methods.POST, methods.PUT, methods.DELETE],
}),
);
app.get("/", (ctx) => {
ctx.ok({ message: "Hello World from CRUD fang!" });
});
app.group("/product", ProductController.register);
app.onError((error, ctx) => {
if (error instanceof NotFoundException)
return ctx.notFound({ message: error.message });
return ctx.internalError({ message: error.message });
});
app.listen();
import { Controller, RouteGroup } from "@fang-js/fang";
import { ProductService } from "./product-service.js";
@Controller
export class ProductController {
static register(group: RouteGroup) {
const productService = new ProductService();
group.get("/", productService.getProducts);
group.get("/:id", productService.getProductById);
group.post("/", productService.createProduct);
group.put("/:id", productService.updateProduct);
group.delete("/:id", productService.deleteProduct);
}
}
import { z } from "@fang-js/fang/zod";
export const productBodyScheme = z.object({
name: z.string(),
price: z.number(),
description: z.string().max(255),
stock: z.number(),
});
import { BaseService, Context, Service } from "@fang-js/fang";
import db from "../lib/drizzle.js";
import { productTable } from "../database/schema.js";
import { productBodyScheme } from "./product-scheme.js";
import { eq } from "drizzle-orm/sql";
import { NotFoundException } from "@fang-js/fang/exceptions";
@Service
export class ProductService extends BaseService {
async getProducts(ctx: Context) {
const products = await db.select().from(productTable);
ctx.ok({ products });
}
async getProductById(ctx: Context) {
const { id } = ctx.params;
const [product] = await db
.select()
.from(productTable)
.where(eq(productTable.id, Number(id)));
if (!product)
throw new NotFoundException(`Product with ID ${id} not found`);
ctx.ok({ product });
}
async createProduct(ctx: Context) {
const { name, description, price, stock } =
await ctx.body(productBodyScheme);
const [newProduct] = await db
.insert(productTable)
.values({
name,
description,
price,
stock,
})
.returning();
ctx.created({
message: "Product created successfully!",
product: newProduct,
});
}
async updateProduct(ctx: Context) {
const { id } = ctx.params;
const body = await ctx.body(productBodyScheme);
const [updatedProduct] = await db
.update(productTable)
.set({
...body,
})
.where(eq(productTable.id, Number(id)))
.returning();
if (!updatedProduct)
throw new NotFoundException(`Product with ID ${id} not found`);
ctx.json({
message: "Product updated successfully!",
product: updatedProduct,
});
}
async deleteProduct(ctx: Context) {
const { id } = ctx.params;
const [deletedProduct] = await db
.delete(productTable)
.where(eq(productTable.id, Number(id)))
.returning();
if (!deletedProduct)
throw new NotFoundException(`Product with ID ${id} not found`);
ctx.json({
message: "Product deleted successfully!",
product: deletedProduct,
});
}
}
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
import { DB_FILE_NAME } from "../config/keys.js";
const client = createClient({ url: DB_FILE_NAME });
const db = drizzle({ client });
export default db;