JSON Config File Example: Best Practices for Configuration
JSON is the default format for configuration files in the JavaScript ecosystem and beyond. Learn patterns from real-world config files and how to structure your own.
Why JSON for Configuration?
JSON has become the default configuration format for the JavaScript ecosystem. Tools like npm, TypeScript, ESLint, Prettier, and VS Code all use JSON config files. The format is simple, well-understood, and supported by every programming language. While alternatives like YAML and TOML exist, JSON remains the most widely used choice for tool configuration.
Example: package.json
The package.json file is the heart of every Node.js project. It defines the project metadata, dependencies, and scripts:
{
"name": "my-web-app",
"version": "2.1.0",
"description": "A modern web application built with Next.js",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint . --ext .ts,.tsx",
"test": "jest --coverage",
"format": "prettier --write ."
},
"dependencies": {
"next": "^15.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.23.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@types/react": "^19.0.0",
"eslint": "^9.0.0",
"prettier": "^3.4.0",
"typescript": "^5.6.0"
},
"engines": {
"node": ">=20.0.0"
}
}Notable patterns: version ranges with ^ allow minor updates while preventing breaking changes, the engines field enforces the minimum Node.js version, and "private": true prevents accidental publishing to npm.
Example: tsconfig.json
TypeScript's configuration file controls how your code is compiled:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "preserve",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "next-env.d.ts"],
"exclude": ["node_modules", "dist", ".next"]
}The paths field sets up import aliases so you can write import X from "@/components/X" instead of long relative paths. The include and exclude arrays use glob patterns to control which files TypeScript processes.
Example: ESLint Configuration
ESLint's JSON configuration file defines linting rules for your codebase:
{
"root": true,
"env": {
"browser": true,
"es2024": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"no-console": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"argsIgnorePattern": "^_"
}],
"prefer-const": "error"
}
}The extends array applies shared rule sets in order, with later entries overriding earlier ones. Individual rules use severity levels: "off", "warn", or "error".
Environment-Specific Configuration
A common pattern is to use a base config with environment overrides. Here's an application settings example:
{
"app": {
"name": "MyApp",
"version": "2.1.0"
},
"server": {
"port": 3000,
"host": "0.0.0.0"
},
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp_dev",
"pool": {
"min": 2,
"max": 10
}
},
"logging": {
"level": "debug",
"format": "pretty"
},
"features": {
"darkMode": true,
"betaFeatures": false,
"maxUploadSize": 10485760
}
}In production, you would override specific values — changing the database host, setting the logging level to "error", and enabling different feature flags. Many frameworks support loading config.json, config.production.json, and merging them.
Best Practices for JSON Config Files
- Keep configs minimal. Only include settings that differ from the defaults. Large config files with mostly default values are hard to maintain.
- Never store secrets in JSON. API keys, database passwords, and tokens belong in environment variables, not in checked-in config files.
- Use sensible defaults. Your application should work with minimal configuration. Every required field in the config is a potential setup failure.
- Validate config at startup. Parse and validate your config file when the application starts, not when a value is first accessed. Use a schema validation library like Zod or JSON Schema.
- Document non-obvious settings.Since JSON doesn't support comments, maintain a separate doc or use a companion schema file that describes each option.
Common Mistakes to Avoid
- Committing environment-specific values. Production database credentials in your config file will leak if your repo is public.
- Forgetting JSON doesn't allow comments. Adding
//comments to your JSON config will cause parse errors. Some tools (like VS Code) support JSONC, but standard JSON does not. - Overly nested configuration. Three levels of nesting is usually the maximum for comfortable human editing.
- Inconsistent naming conventions. Pick either
camelCaseorsnake_casefor your keys and use it consistently throughout the file.
Try it yourself: Paste your config file into our JSON Validator to check for syntax errors before deploying.