Dependency Injection (DI) is a design pattern that removes the responsibility of creating dependencies from a class and hands it over to an external system. Instead of a class creating its own objects, it receives them from the outside.
Loose coupling makes code flexible, easier to test, and maintainable. Without DI, classes become tightly coupled and harder to extend or refactor.
In ASP.NET, services are registered in the Program.cs file using builder.Services. The framework automatically injects the required service into endpoints or controllers.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Registering services
builder.Services.AddScoped<IMessageService, EmailMessageService>();
var app = builder.Build();
app.MapGet("/send", (IMessageService service) => {
return service.Send("Hello from DI");
});
app.Run();
public interface IMessageService {
string Send(string message);
}
public class EmailMessageService : IMessageService {
public string Send(string message) => $"Email sent: {message}";
}
Spring Boot uses annotations like @Service and@Autowired to declare beans and inject them automatically into dependent classes.
@Service
public class EmailService implements MessageService {
public String send(String message) {
return "Email sent: " + message;
}
}
@RestController
public class MessageController {
private final MessageService service;
@Autowired
public MessageController(MessageService service) {
this.service = service;
}
@GetMapping("/send")
public String sendMessage() {
return service.send("Hello from DI");
}
}
Express doesn’t have DI built-in, but you can mimic DI by separating services into modules and importing them where needed.
// messageService.js
class MessageService {
send(message) {
return `Message sent: ${message}`;
}
}
module.exports = new MessageService();
// server.js
const express = require("express");
const service = require("./messageService");
const app = express();
app.get("/send", (req, res) => {
res.send(service.send("Hello from DI"));
});
app.listen(3000);
Next.js relies on modular imports. You can define services in separate files and inject them into API routes or components as needed.
// services/messageService.ts
export class MessageService {
send(message: string) {
return `Message sent: ${message}`;
}
}
// pages/api/send.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { MessageService } from "../../services/messageService";
const service = new MessageService();
export default function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ result: service.send("Hello from DI") });
}
Flask also doesn’t have built-in DI, but you can inject dependencies by creating service classes and passing them into routes.
from flask import Flask
class MessageService:
def send(self, message):
return f"Message sent: {message}"
service = MessageService()
app = Flask(__name__)
@app.route("/send")
def send():
return service.send("Hello from DI")
if __name__ == "__main__":
app.run()
Laravel provides a powerful service container. You bind classes in service providers, and Laravel automatically resolves them into controllers or routes.
// app/Services/MessageService.php
namespace AppServices;
class MessageService {
public function send($message) {
return "Message sent: " . $message;
}
}
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(AppServicesMessageService::class, function ($app) {
return new AppServicesMessageService();
});
}
// routes/web.php
use AppServicesMessageService;
Route::get('/send', function (MessageService $service) {
return $service->send("Hello from DI");
});
Dependency Injection is a universal concept supported across many frameworks. It helps developers write modular, testable, and flexible code. By adopting DI, you achieve loose coupling and make your applications easier to scale and maintain.