اسیدها + دارچین: یک دوت عالی

من فردی هستم که از زمانی که شروع به توسعه اولین پروژه هایم کردم (OT Pokémon و اولین وب سایت های من برای Habbo)، همیشه آن را انتخاب کرده ام. SQL خام. صادقانه بگویم، من هنوز واقعاً از نوشتن سؤالات خود و کنترل دقیق تری بر روی این لایه “سطح پایین” لذت می برم. یک ORM من را کاملاً راحت نمی کند، زیرا من قبلاً روزها را صرف تجزیه و تحلیل گزارش ها برای شناسایی و بهینه سازی پرس و جوهای ناکارآمد کرده ام.
با این حال، در بسیاری از پایگاههای کد که با Raw SQL کار میکردم، اکثریت قریب به اتفاق کنترل مهاجرت نداشتند و پایگاه داده نیز نظارت نمیشد. همه چیز بر اساس بداهه کار می کرد: “آیا به یک رشته جدید نیاز دارید؟ جدول تغییر دهید و ستون جدیدی اضافه می کند.” این رویکرد در همه سناریوها به شدت مضر بود، چندین سوال مطرح شد، مانند: “کدام ستون ها را در محیط تولید آپلود کنیم؟”، “چه موجودیت های جدیدی ایجاد شد؟”، “آیا محیط ها هماهنگ هستند؟” – و بسیاری از مشکلات مشابه دیگر.
راه حل مشکلات من
در مواجهه با همه این مشکلات، تصمیم گرفتم از ابزارهای جدیدی استفاده کنم تا روال خود و تیم هایی را که با آنها کار می کردم سالم تر کنم. نمیخواستم انعطافپذیریام را رها کنم، اما میخواستم بهتر خودم را کنترل کنم درجات آزادی از برنامه پس از تحقیقات فراوان، ابزاری را پیدا کردم که به نظر من کاملترین ابزار برای حل این مشکلات است: استعلام، این یک سازنده پرس و جو برای TypeScript است که علاوه بر کاربردی بودن، کاملاً ایمن است – نکته بسیار مهمی برای من. این کتاب به قدری توجه من را به خود جلب کرد که به طور مستقیم و غیرمستقیم شروع به مشارکت فعال در جامعه کردم و افزونه هایی را برای سایر کتابخانه های منبع باز ادغام شده با Kysely ایجاد کردم.
با این حال، یکی از بزرگترین مشکلات هنگام کار با Kysely این است که بر خلاف ORM، موجودیت یا تولید خودکار انواع/اینترفیس ها ندارد. همه این کارها باید به صورت دستی انجام شود که می تواند کمی طاقت فرسا باشد. در طول تحقیقاتم برای راهحلها، ابزاری را پیدا کردم که در نهایت در تمام پروژههایم شامل PostgreSQL از آن استفاده کردم: دارچین. Kanel به طور خودکار تایپ های پایگاه داده را تولید می کند، که کاملا مکمل Kysely است.
علاوه بر این، دارچین دارای یک ویژگی اضافی برای استفاده مستقیم با استعلام: o کانل-سوال. من فعالانه در این مخزن مشارکت داشتهام و به توسعه ویژگیهای جدید، مانند فیلترهای نوع برای جداول مهاجرت و تبدیل اشیاء Zod به camelCase کمک کردهام.
پیکربندی Kysely
من از NestJS برای نشان دادن مثال های زیر استفاده خواهم کرد. بنابراین، اگر برخی از نحو یا چیزی را در کد متوجه نمیشوید، پیشنهاد میکنم اسناد NestJS را بخوانید. به نظر من، این بهترین چارچوب جاوا اسکریپت است – به خصوص اگر می خواهید از جاوا اسکریپت “فرار کنید”. اما این موضوع برای پست دیگری از من است.
اگر میخواهید نمونهها را به طور کامل دنبال کنید، قبل از آن، باید یک مخزن با NestJS اولیه داشته باشید. با این حال، شما همچنین می توانید کد خود را توسعه دهید.
برای شروع، ما باید خودمان را نصب کنیم استعلام، CLI آن و ماژول PostgreSQL برای Node.js.
npm i kysely pg && npm i kysely-ctl --save-dev
در مرحله بعد، ما باید یک فایل پیکربندی در ریشه پروژه برای آن ایجاد کنیم استعلام. من همچنین از پیشوند Knex برای فایل های مهاجرت و seed خود استفاده خواهم کرد.
// kysely.config.ts
import "dotenv/config";
import { defineConfig, getKnexTimestampPrefix } from "kysely-ctl";
import { Pool } from "pg";
export default defineConfig({
dialect: "pg",
dialectConfig: {
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
},
migrations: {
migrationFolder: "src/database/migrations",
getMigrationPrefix: getKnexTimestampPrefix,
},
seeds: {
seedFolder: "src/database/seeds",
getSeedPrefix: getKnexTimestampPrefix,
},
});
بعد، بیایید دستور را اجرا کنیم npx kysely migrate make create_user_table
در ترمینال ما مسئول ایجاد اولین مهاجرت ما خواهد بود. در مرحله بعد، یک جدول کاربری جدید ایجاد می کنیم و پس از انجام، این انتقال را در پایگاه داده خود با دستور اجرا می کنیم npx kysely migrate latest
.
// 20241225222128_create_user_table.ts
import { sql, type Kysely } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("user")
.addColumn("id", "serial", (col) => col.primaryKey())
.addColumn("name", "text", (col) => col.notNull())
.addColumn("email", "text", (col) => col.unique().notNull())
.addColumn("password", "text", (col) => col.notNull())
.addColumn("created_at", "timestamp", (col) =>
col.defaultTo(sql`now()`).notNull(),
)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable("user").execute();
}
با انجام تمام این مراحل، اجازه دهید یک ماژول برای پایگاه داده خود ایجاد کنیم. همچنین توجه داشته باشید که من از یک افزونه Kysely برای تبدیل ستون های خود به camelCase استفاده می کنم.
// src/database/database.module.ts
import { EnvService } from "@/env/env.service";
import { Global, Logger, Module } from "@nestjs/common";
import { CamelCasePlugin, Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
export const DATABASE_CONNECTION = "DATABASE_CONNECTION";
@Global()
@Module({
providers: [
{
provide: DATABASE_CONNECTION,
useFactory: async (envService: EnvService) => {
const dialect = new PostgresDialect({
pool: new Pool({
connectionString: envService.get("DATABASE_URL"),
}),
});
const nodeEnv = envService.get("NODE_ENV");
const db = new Kysely({
dialect,
plugins: [new CamelCasePlugin()],
log: nodeEnv === "dev" ? ["query", "error"] : ["error"],
});
const logger = new Logger("DatabaseModule");
logger.log("Successfully connected to database");
return db;
},
inject: [EnvService],
},
],
exports: [DATABASE_CONNECTION],
})
export class DatabaseModule {}
پیکربندی Kanel
بیایید با نصب وابستگی های خود شروع کنیم.
npm i kanel kanel-kysely --save-dev
سپس، بیایید فایل پیکربندی خود را برای Kanel ایجاد کنیم تا کار خود را شروع کند. توجه داشته باشید که من از برخی افزونهها مانند camelCaseHook (برای تبدیل رابطهایمان به camelCase) و kyselyTypeFilter (برای حذف جداول مهاجرت Kysely) استفاده خواهم کرد، یکی از این ویژگیها من خوشحال شدم که میتوانم مشارکت داشته باشم و کاری را که داشتیم یکسان کنم. راحت تر
// .kanelrc.js
require("dotenv/config");
const { kyselyCamelCaseHook, makeKyselyHook, kyselyTypeFilter } = require("kanel-kysely");
/** @type {import('kanel').Config} */
module.exports = {
connection: {
connectionString: process.env.DATABASE_URL,
},
typeFilter: kyselyTypeFilter,
preDeleteOutputFolder: true,
outputPath: "./src/database/schema",
preRenderHooks: [makeKyselyHook(), kyselyCamelCaseHook],
};
پس از ایجاد فایل، دستور را اجرا می کنیم npx kanel
در ترمینال ما توجه داشته باشید که یک دایرکتوری در مسیر مشخص شده در فایل پیکربندی ایجاد شده است. این دایرکتوری با نام طرحواره شما، در مورد ما، مطابقت دارد عمومیو در داخل آن دو فایل جدید داریم: PublicSchema.ts ه User.ts. احتمالا شما User.ts دقیقا به این صورت خواهد بود:
// @generated
// This file is automatically generated by Kanel. Do not modify manually.
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
/** Identifier type for public.user */
export type UserId = number & { __brand: 'UserId' };
/** Represents the table public.user */
export default interface UserTable {
id: ColumnType<UserId, UserId | undefined, UserId>;
name: ColumnType<string, string, string>;
email: ColumnType<string, string, string>;
password: ColumnType<string, string, string>;
createdAt: ColumnType<Date, Date | string | undefined, Date | string>;
}
export type User = Selectable<UserTable>;
export type NewUser = Insertable<UserTable>;
export type UserUpdate = Updateable<UserTable>;
با این حال، مهمترین چیز فایل خارج از آن دایرکتوری است عمومی، فایل پایگاه داده.ts، زیرا این چیزی است که ما برای درک کل ساختار پایگاه داده خود به Kysely منتقل می کنیم. داخل آرشیو ما app.service.ts، ما ارائه دهنده DatabaseModule خود را تزریق می کنیم و نوع خود را به Kysely منتقل می کنیم پایگاه داده.
// src/app.service.ts
import { Inject, Injectable } from "@nestjs/common";
import { Kysely } from "kysely";
import { DATABASE_CONNECTION } from "./database/database.module";
import Database from "./database/schema/Database";
@Injectable()
export class AppService {
constructor(@Inject(DATABASE_CONNECTION) private readonly db: Kysely<Database>) {}
async findManyUsers() {
const users = await this.db.selectFrom("user").select(["id", "name"]).execute();
return users;
}
}
توجه داشته باشید که تایپ کانل به درستی کار میکند، زیرا ویرایشگر کد ما دقیقاً ستونهایی را که در اولین مهاجرت ایجاد کردهایم پیشنهاد میکند.
ملاحظات نهایی
این دوتایی است که من واقعاً دوست دارم در پروژه های شخصی خود و حتی در محل کار (زمانی که آزادی انجام این کار را داشته باشم) از آنها استفاده کنم. سازنده پرس و جو ابزار ضروری برای همه کسانی است که انعطاف پذیری Raw SQL را دوست دارند، اما مسیر “ایمن تر” را نیز انتخاب می کنند. Kanel همچنین ساعتهای زیادی را از اشکالزدایی و ایجاد تایپهای جدید نجات داده است. اکیداً توصیه می کنم با این دو پروژه ایجاد کنید، مطمئناً پشیمان نخواهید شد.
لینک مخزن: frankenstein-nodejs