Node.js 37 🟢 Introduction to NestJS Enterprise Framework
Node.js has never lacked HTTP frameworks. Express, Fastify, and Koa are all mature and well-documented. What Node.js has lacked is an opinionated architecture. Express gives you a request object and a response object and leaves the rest to you. Fastify gives you a plugin system and a schema validator and leaves the rest to you. Neither tells you where business logic should live, how dependencies should be wired, or how a large application should be organized. The result is that every team invents its own structure, and every codebase is a new set of conventions that new developers must learn from scratch. NestJS exists to solve that problem. It is not a faster Express. It is an architecture layered on top of Express or Fastify, with dependency injection, modules, and decorators as first-class concepts .
NestJS is often described as “Angular for the backend,” and the description is accurate. It borrows Angular’s module system, its dependency injection container, and its decorator-based syntax. A class annotated with @Controller() handles requests. A class annotated with @Injectable() is a provider that can be injected into other classes. A module annotated with @Module() declares its controllers, providers, and imports. The framework reads this metadata at startup and builds a dependency graph, resolving every provider and wiring it into the classes that need it . The developer writes classes and decorators; NestJS handles the instantiation and connection.
The trade-off is weight. NestJS has a higher cold start time than Express or Fastify — roughly double in serverless benchmarks — because of the dependency injection container and module resolution overhead . It requires TypeScript or Babel, because the decorators and metadata reflection depend on compilation. And it has a learning curve, especially for developers who are not familiar with Angular’s patterns or with dependency injection in general . The trade-off is between the speed and simplicity of a minimal framework and the structure and maintainability of an opinionated architecture. For small scripts and prototypes, NestJS is overkill. For enterprise applications with multiple teams and long lifespans, the structure pays for itself.
This chapter covers three areas. First, why NestJS exists — the architecture problem in Node.js, and the specific problems that dependency injection and modules solve. Second, how the core building blocks work — modules, controllers, providers, and the dependency injection container. Third, how the request lifecycle and cross-cutting concerns are organized — the ordered sequence of middleware, guards, interceptors, pipes, and filters, and the scope at which each is bound. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the module graph and the request lifecycle.
Key point: NestJS is an architecture framework, not a faster HTTP server. It provides dependency injection, modules, and decorators to structure Node.js applications. Controllers handle requests, providers contain business logic, and modules declare the boundaries. The request lifecycle runs in a fixed order: middleware → guards → interceptors (pre) → pipes → handler → interceptors (post) → exception filters .
Why NestJS exists
The architecture problem. Node.js has excellent libraries but no standard architecture. A project can organize its code in any way, and the framework does not enforce any structure. This freedom is a strength for small projects and a liability for large ones. As a codebase grows, the absence of structure becomes a cost: new developers must learn the project’s conventions, dependencies become tangled, and testing requires significant setup. NestJS provides an out-of-the-box architecture that solves this problem. It imposes a structure — modules, controllers, providers — and the structure is consistent across projects .
The dependency injection problem. Manual dependency management is tedious and error-prone. A class that needs a database connection, a logger, and a configuration object must create or receive each one. If the database connection is a singleton, every class that needs it must have a reference to it. Testing requires replacing each dependency with a mock manually. NestJS’s dependency injection container solves this. A provider is declared once, and any class that lists it in its constructor receives it. The container resolves the dependency graph, creates instances, and injects them. In tests, the container can be overridden to supply mocks without changing the class under test .
The module-boundary problem. A large application needs boundaries. Without them, any file can import any other file, and the dependency graph becomes a tangle. NestJS modules provide those boundaries. A module declares which providers it exports and which it keeps private. A provider that is not exported cannot be injected into another module. This encapsulation prevents accidental coupling and makes the dependency graph explicit. The imports array of a module declares which other modules’ exports it consumes. The graph of modules is a directed acyclic graph, and the boundaries are enforced by the container .
The cross-cutting-concern problem. Some logic applies to many routes: authentication, logging, validation, error handling. In a minimal framework, this logic is often implemented as middleware, which has access to the request and response but not to the route’s metadata or the handler’s parameters. NestJS provides a richer set of abstractions. Guards run before the handler and can allow or deny access. Interceptors wrap the handler and can transform the request or response. Pipes validate and transform parameters. Exception filters catch errors and format the response. Each of these can be bound globally, at the controller level, or at the route level, giving precise control over where the logic applies .
The platform-agnosticism problem. NestJS is designed to work with multiple HTTP platforms. Express is the default, and Fastify is supported as an alternative. The framework provides an abstraction layer over both, so application code does not depend on the underlying platform. If a project needs to switch from Express to Fastify for performance reasons, the application code does not change. The platform adapter is swapped at bootstrap, and NestJS handles the differences .
The trade-off. NestJS is heavier than Express or Fastify. Its cold start time is roughly double, and its memory footprint is higher . It requires TypeScript or Babel, because decorators and metadata reflection depend on compilation. The learning curve is steeper, especially for developers without Angular or dependency-injection experience . The trade-off is between the speed and simplicity of a minimal framework and the structure and maintainability of an opinionated architecture. NestJS is the right choice for enterprise applications with multiple teams and long lifespans. It is the wrong choice for a small script or a prototype that will be rewritten in a week.
a. Modules — the organizational unit
A module is a class annotated with @Module(). It declares the controllers, providers, and imports that belong to a feature or domain. The @Module() decorator takes an object with four keys: imports, controllers, providers, and exports .
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
The controllers array lists the controllers that belong to this module. The providers array lists the providers that the module owns. The exports array lists the providers that other modules can inject. A provider that is not exported is private to the module — other modules cannot see it, even if they import the module .
The imports array lists other modules whose exported providers this module needs. When a module imports another module, it gains access to the exported providers, but not to the module’s private providers. This is the encapsulation mechanism. A CatsModule that imports a DatabaseModule can inject the DatabaseService if DatabaseModule exports it. If DatabaseModule does not export it, the injection fails at startup .
The root module, AppModule, is the entry point. It imports the feature modules and wires them together. A well-designed AppModule is thin — it declares the feature modules and global configuration, and nothing else. A “god module” that declares every controller and provider in the application is a sign that the module boundaries are wrong .
b. Controllers — handling requests
A controller is a class annotated with @Controller(). It handles incoming requests and returns responses. The decorator takes an optional route prefix, which is prepended to all routes in the controller .
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
async findAll() {
return this.catsService.findAll();
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.catsService.findOne(id);
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
}
The @Get(), @Post(), @Put(), @Delete(), and @Patch() decorators map HTTP methods to handler methods. The path in the decorator is combined with the controller prefix. A @Get(':id') in a controller with @Controller('cats') handles GET /cats/:id .
NestJS provides parameter decorators for extracting data from the request. @Body() extracts the request body, @Query() extracts query parameters, @Param() extracts route parameters, and @Headers() extracts headers . These decorators are platform-agnostic; they work with both Express and Fastify.
The standard way to return a response is to return a value from the handler. NestJS serializes the value to JSON and sends it with the appropriate status code. A POST handler returns 201 by default; other methods return 200. The @HttpCode() decorator overrides the status code, and the @Header() decorator sets response headers .
The @Res() decorator injects the underlying platform’s response object, which gives full control over the response. When @Res() is used, NestJS disables its standard response handling for that route, and the handler must send the response manually. This is sometimes necessary for cookies or redirects, but it couples the handler to the platform and should be used sparingly. The @Res({ passthrough: true }) option allows using the response object for specific tasks while leaving the rest to NestJS .
c. Providers and dependency injection
A provider is a class annotated with @Injectable(). It contains business logic, data access, or any other functionality that is not directly tied to handling HTTP requests. Providers are the building blocks of the application’s logic, and they are managed by the NestJS IoC container .
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
The @Injectable() decorator attaches metadata to the class, signaling that it can be managed by the container. The class is then declared in the providers array of a module. The container instantiates it once (by default, as a singleton) and injects it into any class that lists it as a constructor parameter .
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
}
The private catsService: CatsService parameter is a TypeScript parameter property. It declares a member and assigns the constructor argument to it. The type annotation CatsService is what NestJS uses to resolve the dependency. The container reads the emitted metadata and supplies an instance of CatsService .
Dependency injection solves several problems at once. It makes dependencies explicit: a class’s constructor lists everything it needs. It makes testing easier: the container can be overridden to supply mocks. It manages lifetimes: the container creates singletons by default and destroys them when the application shuts down. And it enforces the module boundaries: a provider that is not exported cannot be injected into another module .
The container supports more than class-based providers. A provider can be defined with a plain value, a factory function, or an existing token. The useValue form supplies a ready-made object. The useFactory form computes the value, optionally injecting other providers. The useClass form selects an implementation at runtime. The useExisting form creates an alias. These forms are useful for configuration objects, database connections, and swappable implementations .
Complete Example Session
// ============================================
// PART 1: THE ROOT MODULE
// ============================================
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}
// ============================================
// PART 2: A FEATURE MODULE
// ============================================
// cats/cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
// ============================================
// PART 3: A PROVIDER
// ============================================
// cats/cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
}
// ============================================
// PART 4: A CONTROLLER
// ============================================
// cats/cats.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
async findAll() {
return this.catsService.findAll();
}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
}
// ============================================
// PART 5: THE BOOTSTRAP
// ============================================
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
// ============================================
// PART 6: A DTO FOR VALIDATION
// ============================================
// cats/dto/create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
// ============================================
// PART 7: GLOBAL VALIDATION PIPE
// ============================================
// main.ts
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip unknown properties
forbidNonWhitelisted: true, // throw on unknown properties
}));
await app.listen(3000);
}
// ============================================
// PART 8: A GUARD FOR AUTHENTICATION
// ============================================
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
return token === 'Bearer valid-token';
}
}
// Usage on a route:
@UseGuards(AuthGuard)
@Get('protected')
async protected() {
return { status: 'ok' };
}
// ============================================
// PART 9: AN INTERCEPTOR FOR LOGGING
// ============================================
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now();
return next.handle().pipe(
tap(() => console.log(`Request took ${Date.now() - now}ms`)),
);
}
}
// ============================================
// PART 10: AN EXCEPTION FILTER
// ============================================
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
message: exception.message,
timestamp: new Date().toISOString(),
});
}
}
// Usage globally:
app.useGlobalFilters(new HttpExceptionFilter());
The ten parts show the root module, a feature module, a provider, a controller, the bootstrap, a DTO, a global validation pipe, a guard, an interceptor, and an exception filter.
Quick Reference
Core Decorators
| Decorator | Purpose |
|---|---|
@Module() | Declares a module |
@Controller() | Declares a controller |
@Injectable() | Declares a provider |
@Get(), @Post(), etc. | Maps HTTP method to handler |
@Body(), @Query(), @Param() | Extracts request data |
@UseGuards() | Binds a guard to a route or controller |
@UseInterceptors() | Binds an interceptor |
@UsePipes() | Binds a pipe |
@UseFilters() | Binds an exception filter |
Module Keys
| Key | Purpose |
|---|---|
imports | Other modules whose exports are needed |
controllers | Controllers owned by this module |
providers | Providers owned by this module |
exports | Providers visible to other modules |
Request Lifecycle Order
| Order | Phase | Purpose |
|---|---|---|
| 1 | Middleware | Raw request processing |
| 2 | Guards | Allow/deny access |
| 3 | Interceptors (pre) | Pre-handler logic |
| 4 | Pipes | Validate/transform parameters |
| 5 | Handler | Route logic |
| 6 | Interceptors (post) | Post-handler logic |
| 7 | Exception filters | Catch and format errors |
Custom Provider Forms
| Form | Use when |
|---|---|
useClass | Swap implementation by class |
useValue | Supply a ready-made object |
useFactory | Compute value with dependencies |
useExisting | Alias an existing token |
Best Practices
✅ Do This:
// Keep AppModule thin
@Module({ imports: [CatsModule, UsersModule] }) // ✅
// Export only what other modules need
exports: [CatsService] // ✅
// Use DTOs for request bodies
async create(@Body() createCatDto: CreateCatDto) { } // ✅
// Use global validation pipe
app.useGlobalPipes(new ValidationPipe({ whitelist: true })); // ✅
// Bind cross-cutting concerns at the right scope
@UseGuards(AuthGuard) // ✅
// Use constructor injection with private
constructor(private catsService: CatsService) {} // ✅
❌ Don’t Do This:
// Don't declare every provider in AppModule
@Module({ providers: [CatsService, UsersService, AuthService, ...] }) // ❌
// Don't re-declare a provider in two modules
// Two singletons, silent state bugs // ❌
// Don't use @Res() unless necessary
findAll(@Res() res: Response) { res.json(...) } // ❌
// Don't rely on interfaces for injection
constructor(private config: AppConfig) {} // AppConfig is an interface // ❌
// Don't forget to export a provider another module needs
@Module({ providers: [CatsService] }) // no exports // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| “Nest can’t resolve dependencies” | Interface used as token, or provider not exported | Use a class or @Inject() with a token |
| Provider not available in another module | Not exported | Add to exports |
| Two instances of a singleton | Provider re-declared in two modules | Declare once, import the module |
| Guard cannot see validated body | Guards run before pipes | Move logic to interceptor or handler |
@Res() breaks standard response | NestJS disables serialization | Use { passthrough: true } or avoid |
| Circular dependency | Two modules import each other | Use forwardRef or refactor |
| Validation not applied | ValidationPipe not registered | Register globally or per-route |
Real-World Examples
1. Feature Module
@Module({ controllers: [CatsController], providers: [CatsService], exports: [CatsService] })
export class CatsModule {}
2. Root Module
@Module({ imports: [CatsModule, UsersModule] })
export class AppModule {}
3. Constructor Injection
constructor(private catsService: CatsService) {}
4. Global Validation
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
5. Route Guard
@UseGuards(AuthGuard)
@Get('protected')
protected() { }
6. Logging Interceptor
intercept(context, next) { return next.handle().pipe(tap(...)); }
7. Exception Filter
@Catch(HttpException)
export class HttpExceptionFilter { }
8. Custom Provider
{ provide: 'DATABASE', useFactory: () => new Database(), inject: [] }
9. DTO
export class CreateCatDto { name: string; age: number; breed: string; }
10. Bootstrap
const app = await NestFactory.create(AppModule);
await app.listen(3000);
Visual
The Module Graph
┌──────────────────────────────────────────────────────────────┐
│ MODULE GRAPH │
│ │
│ AppModule (root) │
│ │ │
│ ├── imports: CatsModule │
│ │ │ │
│ │ ├── controllers: CatsController │
│ │ ├── providers: CatsService │
│ │ └── exports: CatsService │
│ │ │
│ ├── imports: UsersModule │
│ │ │ │
│ │ ├── imports: DatabaseModule │
│ │ ├── controllers: UsersController │
│ │ ├── providers: UsersService │
│ │ └── exports: UsersService │
│ │ │
│ └── imports: DatabaseModule │
│ │ │
│ ├── providers: DatabaseService │
│ └── exports: DatabaseService │
│ │
│ UsersModule imports DatabaseModule to inject DatabaseService│
│ into UsersService. │
│ │
│ CatsModule does not import DatabaseModule. │
│ It cannot inject DatabaseService. │
│ │
└──────────────────────────────────────────────────────────────┘
Dependency Injection Resolution
┌──────────────────────────────────────────────────────────────┐
│ DEPENDENCY INJECTION RESOLUTION │
│ │
│ @Controller('cats') │
│ export class CatsController { │
│ constructor(private catsService: CatsService) {} │
│ } │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 1. NestJS reads constructor metadata: │ │
│ │ parameter type = CatsService │ │
│ │ │ │
│ │ 2. Container looks up CatsService │ │
│ │ │ │
│ │ 3. Finds it in CatsModule.providers │ │
│ │ │ │
│ │ 4. Creates instance (singleton) │ │
│ │ │ │
│ │ 5. Injects into CatsController constructor │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ The type annotation is the token. │
│ Interfaces and type aliases are erased, so they cannot │
│ be used as tokens. │
│ │
└──────────────────────────────────────────────────────────────┘
Request Lifecycle
┌──────────────────────────────────────────────────────────────┐
│ REQUEST LIFECYCLE │
│ │
│ Request │
│ │ │
│ ▼ │
│ Middleware ──────────────► raw request processing │
│ │ │
│ ▼ │
│ Guards ──────────────────► allow/deny access │
│ │ │
│ ▼ │
│ Interceptors (pre) ──────► pre-handler logic │
│ │ │
│ ▼ │
│ Pipes ───────────────────► validate/transform params │
│ │ │
│ ▼ │
│ Handler ─────────────────► route logic │
│ │ │
│ ▼ │
│ Interceptors (post) ─────► post-handler logic │
│ │ │
│ ▼ │
│ Exception Filters ───────► catch and format errors │
│ │ │
│ ▼ │
│ Response │
│ │
│ Guards run BEFORE pipes. A guard cannot see the validated │
│ DTO. Filters catch everything thrown downstream. │
│ │
└──────────────────────────────────────────────────────────────┘
Provider Scope
┌──────────────────────────────────────────────────────────────┐
│ PROVIDER SCOPE │
│ │
│ DEFAULT (Singleton): │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ One instance per application │ │
│ │ │ │
│ │ CatsService ──► instance ──► injected everywhere │ │
│ │ │ │
│ │ Created at bootstrap, destroyed at shutdown. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ REQUEST-SCOPED: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ One instance per request │ │
│ │ │ │
│ │ Request A ──► instance A │ │
│ │ Request B ──► instance B │ │
│ │ │ │
│ │ For request-specific state, like current user. │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ TRANSIENT: │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ New instance every injection │ │
│ │ │ │
│ │ Class A ──► instance 1 │ │
│ │ Class B ──► instance 2 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| NestJS | Architecture framework for Node.js |
| Platform | Express (default) or Fastify |
| Language | TypeScript (or JavaScript with Babel) |
| Core concept | Dependency injection via IoC container |
| Module | Organizational unit with imports, controllers, providers, exports |
| Controller | Handles HTTP requests with decorators |
| Provider | Injectable class containing business logic |
| Request lifecycle | Middleware → guards → interceptors → pipes → handler → filters |
| Cold start | Higher than Express/Fastify (~2x) |
| Best for | Enterprise applications, large teams |
Key takeaways:
- NestJS is an architecture framework, not a faster HTTP server. It provides dependency injection, modules, and decorators to structure Node.js applications. The value is in the structure, not the raw performance .
- Dependency injection is the core mechanism. Providers are declared once and injected wherever needed. The container resolves the dependency graph, manages lifetimes, and makes testing easier by allowing overrides .
- Modules define boundaries. A module’s
exportsis its public API. A provider that is not exported is private. Theimportsarray declares which other modules’ exports are consumed. The module graph is a directed acyclic graph . - Controllers handle requests; providers contain logic. Controllers should be thin — they extract request data, delegate to a provider, and return the result. Business logic belongs in providers, not controllers .
- The request lifecycle has a fixed order. Middleware runs first, then guards, then interceptors, then pipes, then the handler, then interceptors again, then filters. Guards cannot see the validated DTO because pipes run after guards .
- Cross-cutting concerns are bound at a chosen scope. Guards, interceptors, pipes, and filters can be applied globally, at the controller level, or at the route level. Bind them at the narrowest scope that covers the target .
- The learning curve is steeper than Express or Fastify. The decorator syntax and dependency injection are unfamiliar to developers without Angular experience. The structure is the benefit, but it must be learned .
- NestJS is platform-agnostic. It runs on Express by default and can switch to Fastify. The application code does not depend on the underlying platform, which makes the choice of HTTP framework an infrastructure decision, not an application decision .
Remember: NestJS is the answer to a question that Express and Fastify deliberately leave open: how should a large Node.js application be organized? Its answer is modules, controllers, providers, and dependency injection. A module defines a boundary and declares what it exposes. A controller handles HTTP requests and delegates to providers. A provider contains the logic and is injected wherever it is needed. The container wires everything together, resolving the dependency graph at startup and enforcing the module boundaries. The request lifecycle gives cross-cutting concerns a place to live, ordered from the raw request to the formatted response. The cost is weight — a higher cold start, a steeper learning curve, and a requirement for TypeScript or Babel. The benefit is consistency: every NestJS application has the same structure, the same patterns, and the same separation of concerns. For enterprise applications with multiple teams and long lifespans, the consistency is worth the cost. For small scripts and prototypes, it is not.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!