Skip to content

Commit e54e698

Browse files
authored
Merge pull request #10 from foyzulkarim/feature/05-configuration-management
Implement environment-based config loading with Joi validation
2 parents c0eadd5 + 7eda134 commit e54e698

File tree

14 files changed

+169
-54
lines changed

14 files changed

+169
-54
lines changed

.gitignore

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,6 @@ web_modules/
7272
# Yarn Integrity file
7373
.yarn-integrity
7474

75-
# dotenv environment variable files
76-
.env
77-
.env.development.local
78-
.env.test.local
79-
.env.production.local
80-
.env.local
81-
8275
# parcel-bundler cache (https://parceljs.org/)
8376
.cache
8477
.parcel-cache
@@ -128,3 +121,11 @@ dist
128121
.yarn/build-state.yml
129122
.yarn/install-state.gz
130123
.pnp.*
124+
125+
# ignore .env and config.{}.json files
126+
.env.development
127+
.env.production
128+
.env.test
129+
config.development.json
130+
config.production.json
131+
config.test.json

src/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MONGODB_URI=mongodb://localhost:27017/your-database-name

src/.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MONGODB_URI=mongodb://localhost:27018/testdb

src/configs/config.example.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"NODE_ENV": "development",
3+
"MONGODB_URI": "mongodb://localhost:27017/mydatabase",
4+
"RATE": 40,
5+
"PORT": 4000
6+
}

src/configs/config.schema.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const Joi = require("joi");
2+
3+
const schema = Joi.object({
4+
NODE_ENV: Joi.string()
5+
.valid("development", "production", "test")
6+
.default("development"),
7+
MONGODB_URI: Joi.string().required(),
8+
RATE: Joi.number().min(0).required(),
9+
PORT: Joi.number().min(1000).default(4000),
10+
});
11+
12+
module.exports = schema;

src/configs/config.shared.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"RATE": 100
3+
}

src/configs/config.test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

src/configs/index.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const dotenv = require("dotenv");
2+
const fs = require("fs");
3+
const path = require("path");
4+
5+
const logger = require("../libraries/log/logger");
6+
const schema = require("./config.schema");
7+
8+
class Config {
9+
constructor() {
10+
if (!Config.instance) {
11+
logger.info("Loading and validating config for the first time...");
12+
this.config = this.loadAndValidateConfig();
13+
Config.instance = this;
14+
logger.info("Config loaded and validated");
15+
}
16+
17+
return Config.instance;
18+
}
19+
20+
loadAndValidateConfig() {
21+
const environment = process.env.NODE_ENV || "development";
22+
23+
// 1. Load environment file from one level up and using __dirname
24+
const envFile = `.env.${environment}`;
25+
const envPath = path.join(__dirname, "..", envFile);
26+
if (!fs.existsSync(envPath)) {
27+
throw new Error(`Environment file not found: ${envPath}`);
28+
}
29+
dotenv.config({ path: envPath });
30+
31+
// 2. Load config file based on environment
32+
// Construct the path to the config file
33+
const configFile = path.join(__dirname, `config.${environment}.json`);
34+
35+
// Check if the file exists before trying to read it
36+
if (!fs.existsSync(configFile)) {
37+
throw new Error(`Config file not found: ${configFile}`);
38+
}
39+
40+
let config = JSON.parse(fs.readFileSync(configFile));
41+
42+
const sharedConfigFile = path.join(__dirname, "config.shared.json");
43+
if (fs.existsSync(sharedConfigFile)) {
44+
const sharedConfig = JSON.parse(fs.readFileSync(sharedConfigFile));
45+
config = { ...sharedConfig, ...config };
46+
}
47+
48+
const finalConfig = {};
49+
for (const key in schema.describe().keys) {
50+
if (process.env.hasOwnProperty(key)) {
51+
finalConfig[key] = process.env[key]; // Prioritize environment variables
52+
} else if (config.hasOwnProperty(key)) {
53+
finalConfig[key] = config[key]; // Fallback to config file value
54+
}
55+
}
56+
57+
// 4. load the schema file
58+
if (!schema) {
59+
throw new Error(`Schema file not found`);
60+
}
61+
62+
const { error, value: validatedConfig } = schema.validate(finalConfig);
63+
if (error) {
64+
const missingProperties = error.details.map((detail) => detail.path[0]);
65+
throw new Error(
66+
`Config validation error: missing properties ${missingProperties}`,
67+
);
68+
}
69+
return validatedConfig;
70+
}
71+
72+
static getInstance() {
73+
if (!Config.instance) {
74+
new Config();
75+
}
76+
return Config.instance;
77+
}
78+
}
79+
80+
module.exports = Config.getInstance().config;

src/domains/product/request.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@ const mongoose = require('mongoose');
33

44
const createSchema = Joi.object().keys({
55
name: Joi.string().required(),
6-
// other properties
6+
description: Joi.string().required(),
7+
price: Joi.number().required(),
8+
inStock: Joi.boolean().optional(),
79
});
810

911
const updateSchema = Joi.object().keys({
1012
name: Joi.string(),
11-
// other properties
13+
description: Joi.string(),
14+
price: Joi.number(),
15+
inStock: Joi.boolean(),
1216
});
1317

1418
const idSchema = Joi.object().keys({

src/libraries/db/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const mongoose = require("mongoose");
2+
const logger = require("../log/logger");
3+
4+
const config = require("../../configs");
5+
6+
const connectWithMongoDb = async () => {
7+
const MONGODB_URI = config.MONGODB_URI;
8+
9+
logger.info("Connecting to MongoDB...");
10+
mongoose.connection.once("open", () => {
11+
logger.info("MongoDB connection is open");
12+
});
13+
mongoose.connection.on("error", (error) => {
14+
logger.error("MongoDB connection error", error);
15+
});
16+
17+
await mongoose.connect(MONGODB_URI, {
18+
autoIndex: true,
19+
autoCreate: true,
20+
});
21+
logger.info("Connected to MongoDB");
22+
};
23+
24+
const disconnectWithMongoDb = async () => {
25+
logger.info("Disconnecting from MongoDB...");
26+
await mongoose.disconnect();
27+
logger.info("Disconnected from MongoDB");
28+
};
29+
30+
module.exports = { connectWithMongoDb, disconnectWithMongoDb };

0 commit comments

Comments
 (0)