From a7ada0aef9c2ad9c227198e3f9b210cd65739a1a Mon Sep 17 00:00:00 2001 From: zulfikar-ditya Date: Fri, 16 Jan 2026 21:27:29 +0700 Subject: [PATCH] feat: envalit for config file --- bun.lock | 3 +++ config/app.config.ts | 52 +++++++++++++++++++++++-------------- config/clickhouse.config.ts | 17 +++++++++--- config/cors.config.ts | 33 +++++++++++++---------- config/database.config.ts | 23 +++++++++++----- config/mail.config.ts | 23 +++++++++++----- config/redis.config.ts | 17 +++++++++--- package.json | 1 + 8 files changed, 116 insertions(+), 53 deletions(-) diff --git a/bun.lock b/bun.lock index 070c952..e595ac3 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ "dayjs": "^1.11.18", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.6", + "envalid": "^8.1.1", "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", "fastify-type-provider-zod": "^6.1.0", @@ -492,6 +493,8 @@ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "envalid": ["envalid@8.1.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vOUfHxAFFvkBjbVQbBfgnCO9d3GcNfMMTtVfgqSU2rQGMFEVqWy9GBuoSfHnwGu7EqR0/GeukQcL3KjFBaga9w=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], diff --git a/config/app.config.ts b/config/app.config.ts index c68ef4f..43f70f6 100644 --- a/config/app.config.ts +++ b/config/app.config.ts @@ -1,3 +1,23 @@ +import { cleanEnv, str, num, url } from "envalid"; + +const env = cleanEnv(process.env, { + APP_NAME: str({ default: "Hono App" }), + APP_PORT: num({ default: 3000 }), + APP_URL: url({ default: "http://localhost:3000" }), + APP_ENV: str({ + default: "development", + choices: ["development", "staging", "production"], + }), + APP_TIMEZONE: str({ default: "UTC" }), + APP_KEY: str({ default: "your-app-key" }), + APP_JWT_SECRET: str({ default: "jwt-secret" }), + APP_JWT_EXPIRES_IN: num({ default: 3600 }), + APP_JWT_REFRESH_SECRET: str({ default: "jwt-refresh-secret" }), + APP_JWT_REFRESH_EXPIRES_IN: num({ default: 604800 }), + LOG_LEVEL: str({ default: "info", choices: ["info", "warn", "debug"] }), + CLIENT_URL: url({ default: "http://localhost:3000" }), +}); + interface IAppConfig { APP_NAME: string; APP_PORT: number; @@ -18,24 +38,16 @@ interface IAppConfig { } export const AppConfig: IAppConfig = { - APP_NAME: process.env.APP_NAME || "Hono App", - APP_PORT: Number(process.env.APP_PORT) || 3000, - APP_URL: process.env.APP_URL || "http://localhost:3000", - APP_ENV: (process.env.APP_ENV || "development") as - | "development" - | "staging" - | "production", - APP_TIMEZONE: process.env.APP_TIMEZONE || "UTC", - APP_KEY: process.env.APP_KEY || "your-app-key", - - APP_JWT_SECRET: process.env.APP_JWT_SECRET || "jwt-secret", - APP_JWT_EXPIRES_IN: Number(process.env.APP_JWT_EXPIRES_IN) || 3600, - APP_JWT_REFRESH_SECRET: - process.env.APP_JWT_REFRESH_SECRET || "jwt-refresh-secret", - APP_JWT_REFRESH_EXPIRES_IN: - Number(process.env.APP_JWT_REFRESH_EXPIRES_IN) || 604800, - - LOG_LEVEL: (process.env.LOG_LEVEL || "info") as "info" | "warn" | "debug", - - CLIENT_URL: process.env.CLIENT_URL || "http://localhost:3000", + APP_NAME: env.APP_NAME, + APP_PORT: env.APP_PORT, + APP_URL: env.APP_URL, + APP_ENV: env.APP_ENV, + APP_TIMEZONE: env.APP_TIMEZONE, + APP_KEY: env.APP_KEY, + APP_JWT_SECRET: env.APP_JWT_SECRET, + APP_JWT_EXPIRES_IN: env.APP_JWT_EXPIRES_IN, + APP_JWT_REFRESH_SECRET: env.APP_JWT_REFRESH_SECRET, + APP_JWT_REFRESH_EXPIRES_IN: env.APP_JWT_REFRESH_EXPIRES_IN, + LOG_LEVEL: env.LOG_LEVEL, + CLIENT_URL: env.CLIENT_URL, }; diff --git a/config/clickhouse.config.ts b/config/clickhouse.config.ts index 7e48576..c64de34 100644 --- a/config/clickhouse.config.ts +++ b/config/clickhouse.config.ts @@ -1,3 +1,12 @@ +import { cleanEnv, str, url } from "envalid"; + +const env = cleanEnv(process.env, { + CLICKHOUSE_HOST: url({ default: "http://localhost:8123" }), + CLICKHOUSE_USER: str({ default: "default" }), + CLICKHOUSE_PASSWORD: str({ default: "" }), + CLICKHOUSE_DATABASE: str({ default: "default" }), +}); + export interface IClickHouseConfig { host: string; user: string; @@ -6,8 +15,8 @@ export interface IClickHouseConfig { } export const clickhouseConfig: IClickHouseConfig = { - host: process.env.CLICKHOUSE_HOST || "http://localhost:8123", - user: process.env.CLICKHOUSE_USER || "default", - password: process.env.CLICKHOUSE_PASSWORD || "", - database: process.env.CLICKHOUSE_DATABASE || "default", + host: env.CLICKHOUSE_HOST, + user: env.CLICKHOUSE_USER, + password: env.CLICKHOUSE_PASSWORD, + database: env.CLICKHOUSE_DATABASE, }; diff --git a/config/cors.config.ts b/config/cors.config.ts index a34e682..76e02eb 100644 --- a/config/cors.config.ts +++ b/config/cors.config.ts @@ -1,3 +1,14 @@ +import { cleanEnv, str, num, bool } from "envalid"; + +const env = cleanEnv(process.env, { + CORS_ORIGIN: str({ default: "*" }), + CORS_METHODS: str({ default: "GET,HEAD,PUT,PATCH,POST,DELETE" }), + CORS_ALLOWED_HEADERS: str({ default: "Content-Type,Authorization" }), + CORS_EXPOSED_HEADERS: str({ default: "" }), + CORS_MAX_AGE: num({ default: 86400 }), + CORS_CREDENTIALS: bool({ default: false }), +}); + type CorsConfigType = { origin: string; methods: string[]; @@ -8,18 +19,14 @@ type CorsConfigType = { }; export const corsConfig: CorsConfigType = { - origin: process.env.CORS_ORIGIN || "*", - methods: process.env.CORS_METHODS - ? process.env.CORS_METHODS.split(",").map((method) => method.trim()) - : ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"], - allowedHeaders: process.env.CORS_ALLOWED_HEADERS - ? process.env.CORS_ALLOWED_HEADERS.split(",").map((header) => header.trim()) - : ["Content-Type", "Authorization"], - exposedHeaders: process.env.CORS_EXPOSED_HEADERS - ? process.env.CORS_EXPOSED_HEADERS.split(",").map((header) => header.trim()) + origin: env.CORS_ORIGIN, + methods: env.CORS_METHODS.split(",").map((method) => method.trim()), + allowedHeaders: env.CORS_ALLOWED_HEADERS.split(",").map((header) => + header.trim(), + ), + exposedHeaders: env.CORS_EXPOSED_HEADERS + ? env.CORS_EXPOSED_HEADERS.split(",").map((header) => header.trim()) : [], - maxAge: process.env.CORS_MAX_AGE - ? parseInt(process.env.CORS_MAX_AGE, 10) - : 86400, // Default to 24 hours - credentials: process.env.CORS_CREDENTIALS === "true", + maxAge: env.CORS_MAX_AGE, + credentials: env.CORS_CREDENTIALS, }; diff --git a/config/database.config.ts b/config/database.config.ts index b5a6d92..98b15e5 100644 --- a/config/database.config.ts +++ b/config/database.config.ts @@ -1,3 +1,15 @@ +import { cleanEnv, str, num } from "envalid"; + +const env = cleanEnv(process.env, { + DATABASE_URL: str({ + default: "postgres://user:password@localhost:5432/mydb", + }), + DB_POOL_MIN: num({ default: 2 }), + DB_POOL_MAX: num({ default: 10 }), + DB_IDLE_TIMEOUT: num({ default: 30000 }), + DB_CONNECTION_TIMEOUT: num({ default: 5000 }), +}); + interface IDatabaseConfig { url: string; pool: { @@ -9,12 +21,11 @@ interface IDatabaseConfig { } export const DatabaseConfig: IDatabaseConfig = { - url: - process.env.DATABASE_URL || "postgres://user:password@localhost:5432/mydb", + url: env.DATABASE_URL, pool: { - min: Number(process.env.DB_POOL_MIN) || 2, - max: Number(process.env.DB_POOL_MAX) || 10, - idleTimeoutMillis: Number(process.env.DB_IDLE_TIMEOUT) || 30000, - connectionTimeoutMillis: Number(process.env.DB_CONNECTION_TIMEOUT) || 5000, + min: env.DB_POOL_MIN, + max: env.DB_POOL_MAX, + idleTimeoutMillis: env.DB_IDLE_TIMEOUT, + connectionTimeoutMillis: env.DB_CONNECTION_TIMEOUT, }, }; diff --git a/config/mail.config.ts b/config/mail.config.ts index 05f1f59..aeaba5c 100644 --- a/config/mail.config.ts +++ b/config/mail.config.ts @@ -1,3 +1,14 @@ +import { cleanEnv, str, num, bool } from "envalid"; + +const env = cleanEnv(process.env, { + MAIL_HOST: str({ default: "smtp.example.com" }), + MAIL_PORT: num({ default: 587 }), + MAIL_SECURE: bool({ default: false }), + MAIL_FROM: str({ default: "Elysia " }), + MAIL_USER: str({ default: "your_email@example.com" }), + MAIL_PASS: str({ default: "your_email_password" }), +}); + interface IMailConfig { host: string; port: number; @@ -10,12 +21,12 @@ interface IMailConfig { } export const MailConfig: IMailConfig = { - host: process.env.MAIL_HOST || "smtp.example.com", - port: process.env.MAIL_PORT ? parseInt(process.env.MAIL_PORT, 10) : 587, - secure: process.env.MAIL_SECURE === "true", - from: process.env.MAIL_FROM || "Elysia ", + host: env.MAIL_HOST, + port: env.MAIL_PORT, + secure: env.MAIL_SECURE, + from: env.MAIL_FROM, auth: { - user: process.env.MAIL_USER || "your_email@example.com", - pass: process.env.MAIL_PASS || "your_email_password", + user: env.MAIL_USER, + pass: env.MAIL_PASS, }, }; diff --git a/config/redis.config.ts b/config/redis.config.ts index 3a8a5ef..e5b0dba 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,3 +1,12 @@ +import { cleanEnv, str, num } from "envalid"; + +const env = cleanEnv(process.env, { + REDIS_HOST: str({ default: "localhost" }), + REDIS_PORT: num({ default: 6379 }), + REDIS_PASSWORD: str({ default: "" }), + REDIS_DB: num({ default: 0 }), +}); + interface IRedisConfig { REDIS_HOST: string; REDIS_PORT: number; @@ -6,8 +15,8 @@ interface IRedisConfig { } export const RedisConfig: IRedisConfig = { - REDIS_HOST: process.env.REDIS_HOST || "localhost", - REDIS_PORT: Number(process.env.REDIS_PORT) || 6379, - REDIS_PASSWORD: process.env.REDIS_PASSWORD || undefined, - REDIS_DB: Number(process.env.REDIS_DB) || 0, + REDIS_HOST: env.REDIS_HOST, + REDIS_PORT: env.REDIS_PORT, + REDIS_PASSWORD: env.REDIS_PASSWORD || undefined, + REDIS_DB: env.REDIS_DB, }; diff --git a/package.json b/package.json index 5b7ae22..364eb0a 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "dayjs": "^1.11.18", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.6", + "envalid": "^8.1.1", "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", "fastify-type-provider-zod": "^6.1.0",