Jimple
A lightweight dependency injection container for Node.js and browsers, built with modern ES6 features and with full support for TypeScript.
Install / Use
/learn @fjorgemota/JimpleREADME
Jimple
Jimple is a lightweight and powerful dependency injection container for JavaScript and TypeScript, built for Node.js and the browser. Inspired by Pimple, it brings clean, flexible dependency management to modern JavaScript projects — with zero dependencies and a minimal API.
Table of Contents
- Features
- Why Dependency Injection?
- Quick Start
- Installation
- Core Concepts
- Advanced Features
- ES6 Proxy Mode
- TypeScript Support
- Modular Configuration with Providers
- API Reference
- Real-World Example
- More Examples
- Extending Jimple
- Performance Tips
- Migration from Other DI Containers
- Documentation
Features
✅ Lightweight - ~1KB minified and gzipped
✅ Zero dependencies - No external dependencies in Node.js
✅ Universal - Works in Node.js and browsers
✅ TypeScript - Fully typed with excellent IDE support
✅ ES6 Proxy support - Modern syntax with property access
✅ Extensible - Easy to extend and customize
✅ Well tested - 100% code coverage
✅ Stable API - Mature, stable API you can depend on
Why Dependency Injection?
Dependency injection helps you write more maintainable, testable code by:
- Decoupling components - Services don't need to know how their dependencies are created
- Improving testability - Easy to swap dependencies with mocks during testing
- Managing complexity - Centralized configuration of how objects are wired together
- Lazy loading - Services are only created when needed
- Singleton by default: Same instance returned on subsequent calls
- Dependency management: Services can depend on other services
Quick Start
npm install jimple@2.0.0-beta.6
import Jimple from "jimple";
// Create container
const container = new Jimple();
// Define a simple service
container.set("logger", (c) => {
return {
log: (msg) => console.log(`[${new Date().toISOString()}] ${msg}`),
};
});
// Define a service that depends on another
container.set("userService", (c) => {
const logger = c.get("logger");
return {
createUser: (name) => {
logger.log(`Creating user: ${name}`);
return { id: Math.random(), name };
},
};
});
// Use your services
const userService = container.get("userService");
const user = userService.createUser("Alice");
Installation
npm
npm install jimple
CDN (Browser)
<script src="https://cdn.jsdelivr.net/npm/jimple@2.0.0-beta.6/dist/Jimple.umd.js"></script>
Import Methods
ES6 Modules
import Jimple from "jimple";
CommonJS
const Jimple = require("jimple");
AMD
define(["jimple"], function (Jimple) {
// Your code here
});
Core Concepts
Services
Services are objects that perform tasks in your application. They're defined as functions that return the service instance:
// Database connection service
container.set("database", (c) => {
const config = c.get("dbConfig");
return new Database(config.host, config.port);
});
// Email service that depends on database
container.set("emailService", (c) => {
const db = c.get("database");
return new EmailService(db);
});
Parameters
Parameters store configuration values, strings, numbers, or any non-function data:
// Configuration parameters
container.set("dbConfig", {
host: "localhost",
port: 5432,
database: "myapp",
});
container.set("apiKey", "abc123");
container.set("isProduction", process.env.NODE_ENV === "production");
Factory Services
When you need a new instance every time instead of a singleton:
container.set(
"httpRequest",
container.factory((c) => {
const config = c.get("httpConfig");
return new HttpRequest(config);
}),
);
// Each call returns a new instance
const req1 = container.get("httpRequest");
const req2 = container.get("httpRequest"); // Different instance
Advanced Features
Protecting Functions
To store an actual function (not a service factory) as a parameter:
container.set(
"utility",
container.protect(() => {
return Math.random() * 100;
}),
);
const utilityFn = container.get("utility"); // Returns the function itself
const result = utilityFn(); // Call the function
Extending Services
Add behavior to existing services:
container.set("logger", (c) => new Logger());
// Extend the logger to add file output
container.extend("logger", (logger, c) => {
logger.addFileHandler("/var/log/app.log");
return logger;
});
Removing Services or Parameters
Remove services or parameters from the container with unset():
container.set("logger", (c) => new Logger());
container.set("apiUrl", "https://api.example.com");
// Remove a service
container.unset("logger");
console.log(container.has("logger")); // false
// Remove a parameter
container.unset("apiUrl");
console.log(container.has("apiUrl")); // false
// Safe to unset non-existent services
container.unset("nonExistent"); // No error thrown
Important Notes:
- Removes the service/parameter completely from the container
- Clears any cached instances and metadata for services
- Cannot be undone - you'll need to re-register the service
- Safe to call on non-existent services (no error thrown)
Optional Dependencies & Defaults
Handle optional services with fallbacks:
container.set("cache", (c) => {
if (c.has("redisConfig")) {
return new RedisCache(c.get("redisConfig"));
}
return new MemoryCache(); // Fallback
});
Raw Service Access
Get the service definition function instead of the service itself:
container.set("database", (c) => new Database());
const dbFactory = container.raw("database");
const db1 = dbFactory(container);
const db2 = dbFactory(container); // Create another instance manually
ES6 Proxy Mode
Use modern JavaScript syntax for a more natural API:
const container = new Jimple();
// Set services using property syntax
container["logger"] = (c) => new Logger();
container["userService"] = (c) => new UserService(c["logger"]);
// Access services as properties
const userService = container.userService;
Limitations:
- Can't overwrite built-in methods (
set,get, etc.) - Accessing non-existent properties throws an error
- TypeScript requires special handling (see below)
TypeScript Support
Jimple provides full TypeScript support with interface definitions:
Basic TypeScript Usage
interface Services {
logger: Logger;
database: Database;
userService: UserService;
apiKey: string;
}
const container = new Jimple<Services>();
container.set("apiKey", "secret-key");
container.set("logger", (c) => new Logger());
container.set("database", (c) => new Database());
container.set(
"userService",
(c) => new UserService(c.get("logger"), c.get("database")),
);
// Type-safe access
const userService: UserService = container.get("userService"); // ✅
const wrong: Database = container.get("userService"); // ❌ Compile error
TypeScript with Proxy Mode
interface Services {
logger: Logger;
userService: UserService;
}
const container = Jimple.create<Services>({
logger: (c) => new Logger(),
userService: (c) => new UserService(c.logger),
});
const userService: UserService = container.userService; // ✅ Type-safe
Note: Due to TypeScript limitations with proxies, you can't set properties directly. Use the set method instead:
container.set("newService", (c) => new Service()); // ✅ Works
container.newService = (c) => new Service(); // ❌ TypeScript error
Modular Configuration with Providers
Organize your container configuration into reusable modules:
Basic Provider
const databaseProvider = {
register(container) {
container.set("dbConfig", {
host: process.env.DB_HOST ?? "localhost",
port: process.env.DB_PORT ?? 5432,
});
container.set("database", (c) => {
const config = c.get("dbConfig");
return new Database(config);
});
},
};
container.register(databaseProvider);
File-based Providers (Node.js)
// providers/database.js
module.exports.register = function (container) {
container.set("database", (c) => new Database(c.get("dbConfig")));
};
// main.js
container.register(require("./providers/database"));
Provider Helper
const { provider } = require("jimple");
module.exports = provider((container) => {
container.set("apiService", (c) => new ApiService(c.get("apiConfig")));
});
Multiple Named Providers
module.exports = {
database: provider((c) => {
c.set("database", () => new Database());
}),
cache: provider((c) => {
c.set("cache", () => new Cache());
}),
};
