Skip to content

Conversation

@Mat0to
Copy link

@Mat0to Mat0to commented Aug 30, 2025

🚀 Volunchain Pull Request

Mark with an x all the checkboxes that apply (like [x])

  • Closes #
  • Added tests (if necessary)
  • Run tests
  • Run formatting
  • Evidence attached
  • Commented the code

📌 Type of Change

  • Documentation (updates to README, docs, or comments)
  • Bug fix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

📝 Changes description


📸 Evidence (A photo is required as evidence)


⏰ Time spent breakdown


🌌 Comments


Thank you for contributing to Volunchain, we are glad that you have chosen us as your project of choice and we hope that you continue to contribute to this great project, so that together we can make our mark at the top!

Summary by CodeRabbit

  • New Features
    • Centralized environment validation with clear startup errors when required variables are missing or invalid.
  • Refactor
    • Unified environment access across the app; standardized PORT and service configs.
  • Chores
    • Overhauled .env.example into labeled sections; replaced multiple DB settings with a single DATABASE_URL.
    • Added Soroban, Stellar/Horizon, Redis, Email settings; updated rate limiting vars; removed legacy Supabase/AWS/S3 keys.
    • Added env-var dependency for configuration loading.
  • Documentation
    • Added guidance on environment validation and common startup errors with fixes.

@coderabbitai
Copy link

coderabbitai bot commented Aug 30, 2025

Walkthrough

Centralizes environment variable access via a new env module, updates config files to use it, revises .env.example and README, adds env-var dependency, removes an unused TypeORM data-source, and switches server startup to use validated env values.

Changes

Cohort / File(s) Summary of changes
Env module (centralized validation)
src/config/env.ts
Added env-var powered loader/validator exporting env with typed, defaulted, and required keys.
Configs switched to env module
src/config/horizon.config.ts, src/config/prisma.ts, src/config/redis.ts, src/config/soroban.config.ts, src/config/supabase.ts, src/config/swagger.config.ts, src/config/winston.config.ts
Replaced process.env access with env.*; behavior otherwise unchanged.
Server bootstrap uses validated env
src/index.ts
Reads PORT from env.PORT instead of process.env.
Legacy removal
src/config/data-source.ts
Deleted unused TypeORM DataSource (AppDataSource) file.
Docs and examples
.env.example, readme.md
Reorganized and updated env keys; added startup env validation notes and common error guidance.
Dependency manifest
package.json
Added dependency: env-var@^7.5.0.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Developer
  participant App as Node App (startup)
  participant Env as env module (env-var)
  participant Cfg as Configs (Prisma/Redis/Supabase/…)
  participant Srv as HTTP Server

  Dev->>App: npm start
  App->>Env: Load & validate required env
  alt Missing/invalid env
    Env-->>App: Throw startup error (message)
    App-->>Dev: Exit with explicit error
  else Env valid
    App->>Cfg: Initialize using env.*
    Cfg-->>App: Ready
    App->>Srv: Listen on env.PORT
    Srv-->>Dev: Server running
  end

  note over Env,Cfg: Centralized env access replaces scattered process.env usage
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Poem

A sprig of envs, all neat in a row,
I thump my paw—time to go!
One burrow for secrets, tidy and bright,
No more carrots scattered in the night.
With env-var nibbles and startup cheer,
The server hops up—spring is here! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/config/winston.config.ts (1)

17-19: Create the logs directory before using file transports

Without mkdir, file transports may fail at runtime if ./logs doesn’t exist.

-import path from "path";
+import path from "path";
+import fs from "fs";
@@
-const logsDir = path.join(process.cwd(), "logs");
+const logsDir = path.join(process.cwd(), "logs");
+fs.mkdirSync(logsDir, { recursive: true });
src/config/soroban.config.ts (1)

7-14: Remove dead fallback and redundant validation; env.ts already enforces requirements

env.SOROBAN_RPC_URL and env.SOROBAN_SERVER_SECRET are marked required in env.ts, so the inline default and post-check are unreachable/redundant.

Apply:

 export const sorobanConfig = {
-  rpcUrl: env.SOROBAN_RPC_URL || "https://soroban-testnet.stellar.org",
+  rpcUrl: env.SOROBAN_RPC_URL,
   serverSecret: env.SOROBAN_SERVER_SECRET,
 };
 
-// Validate required environment variables
-if (!sorobanConfig.serverSecret) {
-  throw new Error("SOROBAN_SERVER_SECRET environment variable is required");
-}

If you want a default RPC URL, set it in env.ts via .default("https://soroban-testnet.stellar.org") and keep usage here as env.SOROBAN_RPC_URL.

🧹 Nitpick comments (11)
package.json (1)

42-42: Pin Node engine to ≥18; env-var v7 is fine and TypeORM must stay

  • env-var@^7.5.0 ships its own TS types—no need for @types/env-var.
  • TypeORM is actively used in src/repository/user.repository.ts; don’t remove it.
  • Add an engines field to package.json to enforce Node 18+:
@@ package.json
   "main": "dist/index.js",
+  "engines": {
+    "node": ">=18"
+  },
   "scripts": {
src/index.ts (2)

26-26: Fix import path to env for consistency.

Use local path like other imports to avoid odd "../src" bounce.

-import { env } from "../src/config/env";
+import { env } from "./config/env";

33-35: Use centralized env for NODE_ENV too.

Keep configuration sourced from env.ts.

-const PORT = env.PORT;
-const ENV = process.env.NODE_ENV || "development";
+const PORT = env.PORT;
+const ENV = env.NODE_ENV;
src/config/prisma.ts (1)

7-14: Tweak Prisma logs by environment (optional).

Reduce noise in production.

-  return new PrismaClient({
-    log: ["query", "info", "warn", "error"],
+  const logs = env.NODE_ENV === "production" ? ["warn", "error"] : ["query", "info", "warn", "error"] as const;
+  return new PrismaClient({
+    log: logs as any,
     datasources: {
       db: {
         url: env.DATABASE_URL,
       },
     },
   });
src/config/supabase.ts (1)

4-5: Drop unnecessary non-null assertions

env-var already guarantees presence. The postfix ! is redundant and can hide misconfig.

-const supabaseUrl = env.SUPABASE_URL!;
-const supabaseKey = env.SUPABASE_ANON_KEY!;
+const supabaseUrl = env.SUPABASE_URL;
+const supabaseKey = env.SUPABASE_ANON_KEY;
src/config/redis.ts (1)

5-5: Remove redundant fallback; env.ts already provides the default

Keeps a single source of truth for defaults.

-  url: env.REDIS_URL || "redis://localhost:6379",
+  url: env.REDIS_URL,
src/config/horizon.config.ts (2)

7-8: Deduplicate defaults; rely on env.ts for fallbacks

Defaults already set in env.ts. Keep config lean and single-sourced.

-  url: env.HORIZON_URL || "https://horizon-testnet.stellar.org",
-  network: env.STELLAR_NETWORK || "testnet",
+  url: env.HORIZON_URL,
+  network: env.STELLAR_NETWORK,

12-23: Optional: drop redundant validations here

env.ts already validates presence and allowed network values; duplicating checks can drift. If you keep them, consider deriving from a shared schema.

src/config/swagger.config.ts (1)

13-16: Consider a toggle env for docs instead of strict NODE_ENV check

A flag like ENABLE_SWAGGER=true is often useful in staging.

Example change (and add ENABLE_SWAGGER to env.ts):

-    if (env.NODE_ENV !== "development") {
+    if (env.NODE_ENV !== "development" && !env.ENABLE_SWAGGER) {
       console.log("⚠️ Swagger is disabled in production mode.");
       return;
     }

In env.ts:

ENABLE_SWAGGER: get("ENABLE_SWAGGER").default("false").asBoolStrict(),
src/config/soroban.config.ts (1)

6-9: Add an explicit type for the exported config

Improves readability and editor support.

+type SorobanConfig = { rpcUrl: string; serverSecret: string };
-export const sorobanConfig = {
+export const sorobanConfig: SorobanConfig = {
.env.example (1)

12-13: Resolve dotenv-linter warnings: reorder keys, quote message, ensure trailing newline

Alphabetize keys within sections, quote values with spaces, add final newline.

 # Supabase
-SUPABASE_URL=https://your-supabase-url.supabase.co
-SUPABASE_ANON_KEY=your_supabase_anon_key
+SUPABASE_ANON_KEY=your_supabase_anon_key
+SUPABASE_URL=https://your-supabase-url.supabase.co

 # Email
-EMAIL_SERVICE=gmail
-[email protected]
-EMAIL_PASSWORD=your_email_password
-BASE_URL=http://localhost:3000
+BASE_URL=http://localhost:3000
+EMAIL_PASSWORD=your_email_password
+EMAIL_SERVICE=gmail
+[email protected]

 # Rate Limiting
-RATE_LIMIT_WINDOW_MS=900000
-RATE_LIMIT_MAX_REQUESTS=100
-RATE_LIMIT_MESSAGE=Too many requests, please try again later.
+RATE_LIMIT_MAX_REQUESTS=100
+RATE_LIMIT_MESSAGE="Too many requests, please try again later."
+RATE_LIMIT_WINDOW_MS=900000
+

Also applies to: 23-26, 33-35, 37-38

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23a1f32 and 79cee87.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • .env.example (1 hunks)
  • package.json (1 hunks)
  • readme.md (2 hunks)
  • src/config/data-source.ts (0 hunks)
  • src/config/env.ts (1 hunks)
  • src/config/horizon.config.ts (1 hunks)
  • src/config/prisma.ts (2 hunks)
  • src/config/redis.ts (1 hunks)
  • src/config/soroban.config.ts (1 hunks)
  • src/config/supabase.ts (1 hunks)
  • src/config/swagger.config.ts (1 hunks)
  • src/config/winston.config.ts (2 hunks)
  • src/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/config/data-source.ts
🧰 Additional context used
🧬 Code graph analysis (8)
src/config/redis.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/winston.config.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/horizon.config.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/supabase.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/swagger.config.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/prisma.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/config/soroban.config.ts (1)
src/config/env.ts (1)
  • env (4-34)
src/index.ts (1)
src/config/env.ts (1)
  • env (4-34)
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 13-13: [UnorderedKey] The SUPABASE_ANON_KEY key should go before the SUPABASE_URL key

(UnorderedKey)


[warning] 25-25: [UnorderedKey] The EMAIL_PASSWORD key should go before the EMAIL_SERVICE key

(UnorderedKey)


[warning] 26-26: [UnorderedKey] The BASE_URL key should go before the EMAIL_PASSWORD key

(UnorderedKey)


[warning] 34-34: [UnorderedKey] The RATE_LIMIT_MAX_REQUESTS key should go before the RATE_LIMIT_WINDOW_MS key

(UnorderedKey)


[warning] 35-35: [UnorderedKey] The RATE_LIMIT_MESSAGE key should go before the RATE_LIMIT_WINDOW_MS key

(UnorderedKey)


[warning] 35-35: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 38-38: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🔇 Additional comments (6)
src/config/prisma.ts (2)

3-4: LGTM on centralizing DATABASE_URL via env.

Consistent with the new env module.


27-29: Dev-only global prisma cache is correct.

Prevents hot-reload client churn; good.

src/config/winston.config.ts (1)

3-3: Migration to env module looks good

Import shift to centralized env is correct and aligns with the PR’s objective.

src/config/supabase.ts (1)

2-2: LGTM on centralizing env access

Consistent with the new env.ts.

src/config/redis.ts (1)

1-15: No action needed: redisClient.connect() is already invoked during app startup (initializeRedis in src/index.ts).

src/config/swagger.config.ts (1)

5-5: LGTM on switching to env.NODE_ENV

Matches the centralized env pattern.

Comment on lines 4 to 38
# Core Environment Variables
NODE_ENV=development
PORT=3000
JWT_SECRET=your-jwt-secret
JWT_SECRET=Zq0gGppr1PIqJsYbCJptI+xmgVvd3BViQUEw2nvsiBs=
DB_TYPE=postgres
# DB_HOST=db
# DB_PORT=5432
DB_HOST=localhost
DB_PORT=5434

# DIRECT_URL=DATABASE_URL=postgresql://user:password@localhost:5432/volunchain?schema=public&connection_limit=10&pool_timeout=30&idle_timeout=30
DIRECT_URL=postgresql://volunchain:volunchain123@localhost:5434/volunchain
DATABASE_CONNECTION_LIMIT=10
DATABASE_POOL_TIMEOUT=30
DATABASE_IDLE_TIMEOUT=30
DATABASE_CONNECTION_TIMEOUT=10
ENABLE_QUERY_LOGGING=true
QUERY_TIMEOUT=30000


DB_USER=volunchain
DB_PASSWORD=volunchain123
DB_NAME=volunchain
NODE_ENV="development"
BACKBLAZE_APPLICATION_KEY_ID=
BACKBLAZE_APPLICATION_KEY=
BACKBLAZE_BUCKET_ID=

RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_WINDOW_MINUTES=5
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/volunchain

RATE_LIMIT_AUTH_MAX=10
RATE_LIMIT_AUTH_WINDOW=3
# JWT / Security
JWT_SECRET=your_jwt_secret

RATE_LIMIT_WALLET_MAX=50
RATE_LIMIT_WALLET_WINDOW=5
# Supabase
SUPABASE_URL=https://your-supabase-url.supabase.co
SUPABASE_ANON_KEY=your_supabase_anon_key

RATE_LIMIT_EMAIL_MAX=20
RATE_LIMIT_EMAIL_WINDOW=5
# Redis
REDIS_URL=redis://localhost:6379

RATE_LIMIT_DEFAULT_MAX=100
RATE_LIMIT_DEFAULT_WINDOW=5
# Stellar / Horizon
HORIZON_URL=https://horizon-testnet.stellar.org
STELLAR_NETWORK=testnet

AWS_ACCESS_KEY_ID=your-access-key-here
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_REGION=eu-north-1
S3_BUCKET_NAME=volunchain-certificates-test
SOROBAN_RPC_URL=your_rpc_url
SOROBAN_SERVER_SECRET=your_server_secret
# Email
EMAIL_SERVICE=gmail
EMAIL_USER=[email protected]
EMAIL_PASSWORD=your_email_password
BASE_URL=http://localhost:3000

# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
# Soroban
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
SOROBAN_SERVER_SECRET=your_soroban_server_secret

# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
RATE_LIMIT_MESSAGE=Too many requests, please try again later.

# Email Verification
EMAIL_SECRET=your_email_verification_secret
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Align example vars with validated set

.env.example includes EMAIL_SECRET and RATE_LIMIT_* which are not present in env.ts. Either add them to env.ts (with defaults/required) or remove them from the example to avoid drift.

Quick check script:


🏁 Script executed:

#!/bin/bash
# Show where these are referenced; if unused, drop from example, else add to env.ts
rg -nP -C2 '\b(EMAIL_SECRET|RATE_LIMIT_(WINDOW_MS|MAX_REQUESTS|MESSAGE))\b' src || true

Length of output: 2809


🏁 Script executed:

#!/bin/bash
find . -type f -name "env.ts"

Length of output: 49


🏁 Script executed:

#!/bin/bash
cat src/config/env.ts

Length of output: 1249


Sync environment variables between .env.example and env.ts
.env.example declares EMAIL_SECRET and RATE_LIMIT_WINDOW_MS/MAX_REQUESTS/MESSAGE but src/config/env.ts doesn’t include these keys. Add them to src/config/env.ts with appropriate defaults or required settings, or remove/rename them in .env.example to avoid configuration drift.

🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 13-13: [UnorderedKey] The SUPABASE_ANON_KEY key should go before the SUPABASE_URL key

(UnorderedKey)


[warning] 25-25: [UnorderedKey] The EMAIL_PASSWORD key should go before the EMAIL_SERVICE key

(UnorderedKey)


[warning] 26-26: [UnorderedKey] The BASE_URL key should go before the EMAIL_PASSWORD key

(UnorderedKey)


[warning] 34-34: [UnorderedKey] The RATE_LIMIT_MAX_REQUESTS key should go before the RATE_LIMIT_WINDOW_MS key

(UnorderedKey)


[warning] 35-35: [UnorderedKey] The RATE_LIMIT_MESSAGE key should go before the RATE_LIMIT_WINDOW_MS key

(UnorderedKey)


[warning] 35-35: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 38-38: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🤖 Prompt for AI Agents
In .env.example lines 1-38 and src/config/env.ts, environment vars are out of
sync: .env.example defines EMAIL_SECRET and
RATE_LIMIT_WINDOW_MS/RATE_LIMIT_MAX_REQUESTS/RATE_LIMIT_MESSAGE but
src/config/env.ts does not. Update src/config/env.ts to read and export these
keys (with sensible defaults/types: RATE_LIMIT_WINDOW_MS as number (default
900000), RATE_LIMIT_MAX_REQUESTS as number (default 100), RATE_LIMIT_MESSAGE as
string (default "Too many requests, please try again later."), EMAIL_SECRET as
required or default empty string) or alternatively remove/rename those keys from
.env.example to match env.ts; ensure parsing/validation and types align with the
rest of the config module.

Comment on lines +581 to +597
## ⚠️ Environment Validation & Startup Errors

VolunChain validates all required environment variables at startup. If any required variable is missing or invalid, the application will fail to start and display a clear error message indicating which variable is missing.

**Common startup failure messages:**
- `DATABASE_URL environment variable is required`
- `JWT_SECRET environment variable is required`
- `SUPABASE_URL environment variable is required`
- `SUPABASE_ANON_KEY environment variable is required`
- `EMAIL_USER environment variable is required`
- `EMAIL_PASSWORD environment variable is required`
- `SOROBAN_SERVER_SECRET environment variable is required`

**How to fix:**
Check your `.env` file and ensure all required variables are set. See `.env.example` for a complete list of required variables and example values.

If you see a startup error, update your `.env` file and restart the application.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Document all required envs: missing SOROBAN_RPC_URL in failures list.

env.ts marks SOROBAN_RPC_URL as required; add it to keep docs aligned with runtime validation.

Apply:

 **Common startup failure messages:**
 - `DATABASE_URL environment variable is required`
 - `JWT_SECRET environment variable is required`
 - `SUPABASE_URL environment variable is required`
 - `SUPABASE_ANON_KEY environment variable is required`
 - `EMAIL_USER environment variable is required`
 - `EMAIL_PASSWORD environment variable is required`
+- `SOROBAN_RPC_URL environment variable is required`
 - `SOROBAN_SERVER_SECRET environment variable is required`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## ⚠️ Environment Validation & Startup Errors
VolunChain validates all required environment variables at startup. If any required variable is missing or invalid, the application will fail to start and display a clear error message indicating which variable is missing.
**Common startup failure messages:**
- `DATABASE_URL environment variable is required`
- `JWT_SECRET environment variable is required`
- `SUPABASE_URL environment variable is required`
- `SUPABASE_ANON_KEY environment variable is required`
- `EMAIL_USER environment variable is required`
- `EMAIL_PASSWORD environment variable is required`
- `SOROBAN_SERVER_SECRET environment variable is required`
**How to fix:**
Check your `.env` file and ensure all required variables are set. See `.env.example` for a complete list of required variables and example values.
If you see a startup error, update your `.env` file and restart the application.
## ⚠️ Environment Validation & Startup Errors
VolunChain validates all required environment variables at startup. If any required variable is missing or invalid, the application will fail to start and display a clear error message indicating which variable is missing.
**Common startup failure messages:**
- `DATABASE_URL environment variable is required`
- `JWT_SECRET environment variable is required`
- `SUPABASE_URL environment variable is required`
- `SUPABASE_ANON_KEY environment variable is required`
- `EMAIL_USER environment variable is required`
- `EMAIL_PASSWORD environment variable is required`
- `SOROBAN_RPC_URL environment variable is required`
- `SOROBAN_SERVER_SECRET environment variable is required`
**How to fix:**
Check your `.env` file and ensure all required variables are set. See `.env.example` for a complete list of required variables and example values.
If you see a startup error, update your `.env` file and restart the application.
🤖 Prompt for AI Agents
In readme.md around lines 581 to 597, the startup failure message list omits
SOROBAN_RPC_URL even though env.ts treats it as required; update the "Common
startup failure messages" section to include a bullet `SOROBAN_RPC_URL
environment variable is required` and, if applicable, mention it in the "How to
fix" guidance (ensure .env.example shows SOROBAN_RPC_URL) so the docs match
runtime validation.

Comment on lines +1 to +34
import { get } from "env-var";

// Use 'get' directly instead of 'env.get'
export const env = {
NODE_ENV: get("NODE_ENV").default("development").asString(),
PORT: get("PORT").default("3000").asPortNumber(),

// Database
DATABASE_URL: get("DATABASE_URL").required().asUrlString(),

// JWT / Security
JWT_SECRET: get("JWT_SECRET").required().asString(),

// Supabase
SUPABASE_URL: get("SUPABASE_URL").required().asUrlString(),
SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY").required().asString(),

// Redis
REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asString(),

// Stellar / Horizon
HORIZON_URL: get("HORIZON_URL").default("https://horizon-testnet.stellar.org").asUrlString(),
STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asString(),

// Email
EMAIL_SERVICE: get("EMAIL_SERVICE").default("gmail").asString(),
EMAIL_USER: get("EMAIL_USER").required().asString(),
EMAIL_PASSWORD: get("EMAIL_PASSWORD").required().asString(),
BASE_URL: get("BASE_URL").default("http://localhost:3000").asUrlString(),

// Soroban
SOROBAN_RPC_URL: get("SOROBAN_RPC_URL").required().asUrlString(),
SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET").required().asString(),
}; No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Make env-loading self-contained and tighten validation (enums/URL).

  • Load dotenv here to decouple callers.
  • Validate enums for NODE_ENV/STELLAR_NETWORK.
  • Validate REDIS_URL as URL.
  • Minor: freeze to avoid accidental mutation.

If STELLAR_NETWORK accepts "public" instead of "mainnet" anywhere, adjust enum accordingly. Verify:

Patch:

+import "dotenv/config";
 import { get } from "env-var";

-// Use 'get' directly instead of 'env.get'
-export const env = {
-  NODE_ENV: get("NODE_ENV").default("development").asString(),
+// Use 'get' directly instead of 'env.get'
+export const env = Object.freeze({
+  NODE_ENV: get("NODE_ENV").default("development").asEnum(["development", "test", "production"]),
   PORT: get("PORT").default("3000").asPortNumber(),

   // Database
   DATABASE_URL: get("DATABASE_URL").required().asUrlString(),

   // JWT / Security
   JWT_SECRET: get("JWT_SECRET").required().asString(),

   // Supabase
   SUPABASE_URL: get("SUPABASE_URL").required().asUrlString(),
   SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY").required().asString(),

   // Redis
-  REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asString(),
+  REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asUrlString(),

   // Stellar / Horizon
   HORIZON_URL: get("HORIZON_URL").default("https://horizon-testnet.stellar.org").asUrlString(),
-  STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asString(),
+  STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asEnum(["testnet", "mainnet"]),
@@
   SOROBAN_RPC_URL: get("SOROBAN_RPC_URL").required().asUrlString(),
   SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET").required().asString(),
-};
+} as const);

🏁 Script executed:

#!/bin/bash
rg -nP -C2 --type=ts "STELLAR_NETWORK|['\"](public|mainnet)['\"]"

Length of output: 1691


Load dotenv and tighten env validation

  • Import dotenv in src/config/env.ts to decouple callers.
  • Enforce enums:
    NODE_ENV.asEnum(["development","test","production"])
    STELLAR_NETWORK.asEnum(["testnet","mainnet"])
  • Validate URLs: use .asUrlString() for REDIS_URL.
  • Freeze the env object to prevent mutations.
+import "dotenv/config";
 import { get } from "env-var";

-export const env = {
+export const env = Object.freeze({
+  NODE_ENV: get("NODE_ENV").default("development").asEnum(["development", "test", "production"]),
   PORT: get("PORT").default("3000").asPortNumber(),

   // Database
   DATABASE_URL: get("DATABASE_URL").required().asUrlString(),

   // JWT / Security
   JWT_SECRET: get("JWT_SECRET").required().asString(),

   // Supabase
   SUPABASE_URL: get("SUPABASE_URL").required().asUrlString(),
   SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY").required().asString(),

   // Redis
-  REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asString(),
+  REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asUrlString(),

   // Stellar / Horizon
   HORIZON_URL: get("HORIZON_URL").default("https://horizon-testnet.stellar.org").asUrlString(),
-  STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asString(),
+  STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asEnum(["testnet", "mainnet"]),

   // Email
   EMAIL_SERVICE: get("EMAIL_SERVICE").default("gmail").asString(),
   EMAIL_USER: get("EMAIL_USER").required().asString(),
   EMAIL_PASSWORD: get("EMAIL_PASSWORD").required().asString(),
   BASE_URL: get("BASE_URL").default("http://localhost:3000").asUrlString(),

   // Soroban
   SOROBAN_RPC_URL: get("SOROBAN_RPC_URL").required().asUrlString(),
   SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET").required().asString(),
-};
+} as const);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { get } from "env-var";
// Use 'get' directly instead of 'env.get'
export const env = {
NODE_ENV: get("NODE_ENV").default("development").asString(),
PORT: get("PORT").default("3000").asPortNumber(),
// Database
DATABASE_URL: get("DATABASE_URL").required().asUrlString(),
// JWT / Security
JWT_SECRET: get("JWT_SECRET").required().asString(),
// Supabase
SUPABASE_URL: get("SUPABASE_URL").required().asUrlString(),
SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY").required().asString(),
// Redis
REDIS_URL: get("REDIS_URL").default("redis://localhost:6379").asString(),
// Stellar / Horizon
HORIZON_URL: get("HORIZON_URL").default("https://horizon-testnet.stellar.org").asUrlString(),
STELLAR_NETWORK: get("STELLAR_NETWORK").default("testnet").asString(),
// Email
EMAIL_SERVICE: get("EMAIL_SERVICE").default("gmail").asString(),
EMAIL_USER: get("EMAIL_USER").required().asString(),
EMAIL_PASSWORD: get("EMAIL_PASSWORD").required().asString(),
BASE_URL: get("BASE_URL").default("http://localhost:3000").asUrlString(),
// Soroban
SOROBAN_RPC_URL: get("SOROBAN_RPC_URL").required().asUrlString(),
SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET").required().asString(),
};
import "dotenv/config";
import { get } from "env-var";
// Use 'get' directly instead of 'env.get'
export const env = Object.freeze({
NODE_ENV: get("NODE_ENV")
.default("development")
.asEnum(["development", "test", "production"]),
PORT: get("PORT")
.default("3000")
.asPortNumber(),
// Database
DATABASE_URL: get("DATABASE_URL")
.required()
.asUrlString(),
// JWT / Security
JWT_SECRET: get("JWT_SECRET")
.required()
.asString(),
// Supabase
SUPABASE_URL: get("SUPABASE_URL")
.required()
.asUrlString(),
SUPABASE_ANON_KEY: get("SUPABASE_ANON_KEY")
.required()
.asString(),
// Redis
REDIS_URL: get("REDIS_URL")
.default("redis://localhost:6379")
.asUrlString(),
// Stellar / Horizon
HORIZON_URL: get("HORIZON_URL")
.default("https://horizon-testnet.stellar.org")
.asUrlString(),
STELLAR_NETWORK: get("STELLAR_NETWORK")
.default("testnet")
.asEnum(["testnet", "mainnet"]),
// Email
EMAIL_SERVICE: get("EMAIL_SERVICE")
.default("gmail")
.asString(),
EMAIL_USER: get("EMAIL_USER")
.required()
.asString(),
EMAIL_PASSWORD: get("EMAIL_PASSWORD")
.required()
.asString(),
BASE_URL: get("BASE_URL")
.default("http://localhost:3000")
.asUrlString(),
// Soroban
SOROBAN_RPC_URL: get("SOROBAN_RPC_URL")
.required()
.asUrlString(),
SOROBAN_SERVER_SECRET: get("SOROBAN_SERVER_SECRET")
.required()
.asString(),
} as const);
🤖 Prompt for AI Agents
In src/config/env.ts around lines 1 to 34, import and call dotenv.config() at
the top so environment variables are loaded within this module (decoupling
callers); replace NODE_ENV get chain with
.asEnum(["development","test","production"]) and STELLAR_NETWORK with
.asEnum(["testnet","mainnet"]); change REDIS_URL to use .asUrlString() instead
of .asString(); and after constructing the env object call Object.freeze(env) to
prevent runtime mutations.

Comment on lines 1 to 4
import dotenv from "dotenv";
import { env } from "./env";

dotenv.config();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: dotenv loads after env import due to ESM import order

env.ts evaluates before dotenv.config() here, so .env values won’t be visible to env. Move dotenv loading into env.ts and remove it here.

-import dotenv from "dotenv";
-import { env } from "./env";
-
-dotenv.config();
+import { env } from "./env";

Then in src/config/env.ts (outside this hunk), ensure:

import "dotenv/config"; // load .env before reading variables
🤖 Prompt for AI Agents
In src/config/horizon.config.ts around lines 1 to 4, dotenv is being configured
after importing ./env which causes env.ts to evaluate before .env is loaded;
remove the dotenv.config() import from this file and move the dotenv
initialization into src/config/env.ts so environment variables are available
when env.ts runs; specifically, in src/config/env.ts add a top-level import
(e.g., import "dotenv/config") or call dotenv.config() before any process.env
reads, and then update horizon.config.ts to only import { env } from "./env"
without configuring dotenv locally.

Comment on lines 1 to 4
import dotenv from "dotenv";
import { env } from "./env";

dotenv.config();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

dotenv.config() runs after env import — order-of-operations bug

env.ts is imported before dotenv.config() runs, so variables may be read before .env is loaded. Centralize dotenv initialization in src/config/env.ts and remove it here.

Apply this diff here:

-import dotenv from "dotenv";
-import { env } from "./env";
-
-dotenv.config();
+import { env } from "./env";

And at the very top of src/config/env.ts:

import dotenv from "dotenv";
dotenv.config();

Verify no stray initializations remain:


🏁 Script executed:

#!/bin/bash
rg -nP -C2 'dotenv\.config\s*\(' src

Length of output: 1730


Centralize dotenv initialization in src/config/env.ts and remove all other calls

  • At the top of src/config/env.ts, add:
    import dotenv from "dotenv";
    dotenv.config();
  • In src/config/soroban.config.ts and src/config/horizon.config.ts, remove the import dotenv… and dotenv.config() lines—keep only import { env } from "./env";.
  • Remove all dotenv.config() calls (and their imports) from:
    • src/scripts/sorobanDemo.ts
    • src/shared/infrastructure/utils/setup-s3-bucket.ts
    • src/modules/metrics/application/services/MetricsService.ts
    • src/modules/auth/utils/email.utils.ts
  • Ensure your application’s entry points import env (or “./config/env”) before using any environment variables.
  • Verify no stray initializations remain:
    rg -nP 'dotenv\.config\s*\(' src

Comment on lines +21 to +22
const isProduction = env.NODE_ENV === "production";
const isDevelopment = env.NODE_ENV === "development";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Unify config source and add LOG_LEVEL to env module

Now that NODE_ENV is read from env, LOG_LEVEL should be too for consistency and testability.

Apply within this file:

-  const isProduction = env.NODE_ENV === "production";
-  const isDevelopment = env.NODE_ENV === "development";
+  const isProduction = env.NODE_ENV === "production";
+  const isDevelopment = env.NODE_ENV === "development";
@@
-    level: process.env.LOG_LEVEL || (isProduction ? "info" : "debug"),
+    level: env.LOG_LEVEL || (isProduction ? "info" : "debug"),

And in src/config/env.ts (outside this hunk), add:

import "dotenv/config"; // ensure .env is loaded once, early
export const env = {
  // ...
  LOG_LEVEL: get("LOG_LEVEL").default("info").asEnum(["error","warn","info","http","verbose","debug","silly"]),
};

Also applies to: 74-75

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant