Introducing IntentJS - A delightful NodeJS Framework
The Paradox of Choice in NodeJS The NodeJS ecosystem is rich with frameworks like NestJS, SailsJS, and Adonis. These frameworks offer extensive ecosystems of plugins and extensions, allowing developers to integrate databases, caches, and various services with ease. While this flexibility is powerful, it introduces a significant challenge: decision fatigue. Simplicity is the ultimate sophistication This timeless quote resonates strongly in today's software engineering landscape. The current NodeJS ecosystem, fragmented with countless packages, often complicates development rather than simplifying it. Every developer has their preferences, leading to projects with varying architectures and dependencies. The Need for Consistency During NodeJS, I found myself working on Node projects with vastly different architectures. Experimenting with various packages made it challenging for my team to understand and build upon our codebase. This inconsistency hampered our ability to: Switch contexts quickly Debug efficiently Implement features rapidly Learning from Other Ecosystems Frameworks like Laravel and Ruby on Rails have kept PHP and Ruby relevant by offering superior features and an excellent Developer Experience (DX). Inspired by this, and particularly affirmed by a tweet from Taylor Otwell, I began to envision a similar solution for the NodeJS ecosystem. The Birth of Intent I will be honest, first version of Intent is an aggregation of my past open source works that I have been doing since 2020. It's syntax is similar to NestJS keeping the learning curve extremely low, utilising it's powerful Dependency Injection system. Intent prioritizes Developer Experience (DX), and various integrations like FileSystems, Queues, Cache, etc. Introduction Intent is a web application framework without focus on delightful developer experience. With built-in declarative and elegant APIs out-of-the-box, you can quickly build an production ready scalable application. Some of the features that Intent supports out of the box. Integration Drivers Storage Unix File Systems, S3 Message Queues AWS SQS, Redis Mailers SMTP, Mailgun, Resend Caching Redis, In-Memory Console Commands Logging File Based Logging Exception Handler Sentry Integration Task Scheduling Validations Transformers Helpers Number, Array, Objects and Strings Internationalisation Let's take a quick look at how quickly you can use these integrations. Using Storage Intent supports UNIX File System and AWS S3 right now. All you need to change is the configuration, and done. No change at the code level is needed. // reads a file await Storage.disk("invoices").get("order_1234.pdf"); // uploads a file await Storage.disk("invoices").put("order_23456.pdf", content, { mimeType: "application/pdf" }); Read More - Storage Docs Message Queues For any async tasks, Intent provides support for Redis-based, AWS SQS message queues. Let's take a quick look. First, define a job with Job decorator. import { Injectable } from '@nestjs/common'; import { Job } from '@intentjs/core'; @Injectable() export class NotificationJob { constructor() {} @Job('user_signedup') async create(data: Record) { // write your logic here } } Now, we need to dispatch this job to the queue. Dispatch({ job: "user_signedup", data: { email: "vinayak@tryintent.com", subject: "Thanks for signing up.", }, }); Now you can start consuming the messages using node intent queue:work command in terminal. Read More - Queue Docs Mailers Intent comes with an in-built email template which you can use to build emails. For example, import { MailMessage } from '@intentjs/core'; const mail = MailMessage.init() .greeting('Hey there') .line( 'We received your request to reset your account password.', ) .button('Click here to reset your password', 'https://google.com') .line('Alternative, you can also enter the code below when prompted') .inlineCode('ABCD1234') .line('Rise & Shine,') .line('V') .subject('Hey there from Intent') Above code will output the following email, Now you can simply send the email with any of the support drivers (Resend, Mailgun, SMTP). import { Mail } from "@intentjs/core"; Mail.init() .to("vinayak@tryintent.com") // OR .to(['id1@email.com', 'id2@email.com']) .send(mail); Read More - Mail Docs Caching Intent comes out of the support for Redis and In Memory cache database. Let's see how quickly you can start using cache. // setting value in cache await Cache.store().set("otp", 1234); // getting value from cache await CacheStore().get("otp"); You might also run into situations where the data doesn't exist in the cache, then you can use the remember or rememberForever method. const cb = () => { // your custom logic here, for eg. a db query, an api call.

The Paradox of Choice in NodeJS
The NodeJS ecosystem is rich with frameworks like NestJS, SailsJS, and Adonis. These frameworks offer extensive ecosystems of plugins and extensions, allowing developers to integrate databases, caches, and various services with ease. While this flexibility is powerful, it introduces a significant challenge: decision fatigue.
Simplicity is the ultimate sophistication
This timeless quote resonates strongly in today's software engineering landscape. The current NodeJS ecosystem, fragmented with countless packages, often complicates development rather than simplifying it. Every developer has their preferences, leading to projects with varying architectures and dependencies.
The Need for Consistency
During NodeJS, I found myself working on Node projects with vastly different architectures. Experimenting with various packages made it challenging for my team to understand and build upon our codebase. This inconsistency hampered our ability to:
- Switch contexts quickly
- Debug efficiently
- Implement features rapidly
Learning from Other Ecosystems
Frameworks like Laravel and Ruby on Rails have kept PHP and Ruby relevant by offering superior features and an excellent Developer Experience (DX). Inspired by this, and particularly affirmed by a tweet from Taylor Otwell, I began to envision a similar solution for the NodeJS ecosystem.
The Birth of Intent
I will be honest, first version of Intent is an aggregation of my past open source works that I have been doing since 2020. It's syntax is similar to NestJS keeping the learning curve extremely low, utilising it's powerful Dependency Injection system. Intent prioritizes Developer Experience (DX), and various integrations like FileSystems, Queues, Cache, etc.
Introduction
Intent is a web application framework without focus on delightful developer experience. With built-in declarative and elegant APIs out-of-the-box, you can quickly build an production ready scalable application.
Some of the features that Intent supports out of the box.
Integration | Drivers |
---|---|
Storage | Unix File Systems, S3 |
Message Queues | AWS SQS, Redis |
Mailers | SMTP, Mailgun, Resend |
Caching | Redis, In-Memory |
Console Commands | |
Logging | File Based Logging |
Exception Handler | Sentry Integration |
Task Scheduling | |
Validations | |
Transformers | |
Helpers | Number, Array, Objects and Strings |
Internationalisation |
Let's take a quick look at how quickly you can use these integrations.
Using Storage
Intent supports UNIX File System
and AWS S3
right now. All you need to change is the configuration, and done. No change at the code level is needed.
// reads a file
await Storage.disk("invoices").get("order_1234.pdf");
// uploads a file
await Storage.disk("invoices").put("order_23456.pdf", content, {
mimeType: "application/pdf"
});
Read More - Storage Docs
Message Queues
For any async tasks, Intent provides support for Redis-based, AWS SQS message queues. Let's take a quick look.
First, define a job with Job
decorator.
import { Injectable } from '@nestjs/common';
import { Job } from '@intentjs/core';
@Injectable()
export class NotificationJob {
constructor() {}
@Job('user_signedup')
async create(data: Record<string, any>) {
// write your logic here
}
}
Now, we need to dispatch this job to the queue.
Dispatch({
job: "user_signedup",
data: { email: "vinayak@tryintent.com", subject: "Thanks for signing up.", },
});
Now you can start consuming the messages using node intent queue:work
command in terminal.
Read More - Queue Docs
Mailers
Intent comes with an in-built email template which you can use to build emails. For example,
import { MailMessage } from '@intentjs/core';
const mail = MailMessage.init()
.greeting('Hey there')
.line(
'We received your request to reset your account password.',
)
.button('Click here to reset your password', 'https://google.com')
.line('Alternative, you can also enter the code below when prompted')
.inlineCode('ABCD1234')
.line('Rise & Shine,')
.line('V')
.subject('Hey there from Intent')
Above code will output the following email,
Now you can simply send the email with any of the support drivers (Resend, Mailgun, SMTP).
import { Mail } from "@intentjs/core";
Mail.init()
.to("vinayak@tryintent.com") // OR .to(['id1@email.com', 'id2@email.com'])
.send(mail);
Read More - Mail Docs
Caching
Intent comes out of the support for Redis
and In Memory
cache database. Let's see how quickly you can start using cache.
// setting value in cache
await Cache.store().set("otp", 1234);
// getting value from cache
await CacheStore().get("otp");
You might also run into situations where the data doesn't exist in the cache, then you can use the remember
or rememberForever
method.
const cb = () => {
// your custom logic here, for eg. a db query, an api call.
return [
{ name: 'Shoe Dog', author: 'Phil Knight', },
];
};
await CacheStore().remember("books", cb, 120);
Read More - Cache Docs
Console Commands
You can also write elegant console commands using Intent which you can run in your terminal using node intent command_name
.
import { Injectable } from "@nestjs/common";
import { Command, ConsoleIO } from "@intentjs/core";
@Injectable()
@Command("hello {name=world}", { desc: "Test Command" })
export class HelloWorldCommand {
async handle(_cli: ConsoleIO): Promise<void> {
const name = _cli.argument<string>("name");
_cli.info(`Hello ${name}!`);
return;
}
}
Now you can run the command in your terminal
node intent hello vinayak
Read More - Console Docs
Logging
Intent comes with support for file based logging, You can make use of the file based logging. Let's take a quick look,
import { Log } from '@intentjs/core';
const logger = Log();
logger.debug('hello world!');
logger.verbose('verbose');
logger.info('info');
logger.warn('warn');
logger.error('error', e);
Read More - Logging Docs
Exception Handler
Intent comes with global exception filter for your application, along with integration for Sentry enabling you to track and notify you of errors whenever they happen on production.
import { AppConfig } from '@libs/intent';
import { registerAs } from '@nestjs/config';
export default registerAs(
'app',
() =>
({
// other config here.
sentry: {
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
integrateNodeProfile: true,
},
}) as AppConfig,
);
Read More - Error Handling
Transformers
Intent comes with support for Transformers
which you can use to transform the response objects before you send it out to the client,
the clients can also request for additional data on the fly.
You can read more about it here.
Helpers
I have added multiple helper methods which I feel would be very useful, so that you don't have to pollute your code with small logic.
For example, let's take a look at Numbers
helper
import { Num } from "@intentjs/core";
Num.abbreviate(1200, { precision: 2 });
// 1.2K
Num.abbreviate(1200, { locale: "hi" });
// 1.2 हज़ार
It also has String
helpers, for example.
import { Str } from '@intentjs/core';
Str.pluralize('child');
// children
Str.remove("New OSS NodeJS Framework", "OSS ");
// New NodeJS Framework
If you want to pull some keys from nested objects
, you can do so
import { Obj } from '@intentjs/core';
const obj = {
firstName: "Vinayak",
lastName: "Sarawagi",
email: "vinayak@tryintent.com",
wishlist: [
{ id: 1, name: "Product 1" },
{ id: 2, name: "Product 2" },
],
};
Obj.pick(obj, ["firstName", "lastName", "wishlist.*.id"]);
/**
* {
* firstName: 'Vinayak',
* lastName: 'Sarawagi',
* wishlist: [ { id: 1 }, { id: 2 } ]
* }
*/
Similarly, you can also use Array
helpers, let's say you want to return the array but without some keys.
import { Arr } from '@intentjs/core';
const goats = [
{ name: 'Saina Nehwal', sport: 'Badminton' },
{ name: 'Sunil Chetri', sport: 'Football' },
{ name: 'Rohit Sharma', sport: 'Cricket' },
{ name: 'Virat Kohli', sport: 'Cricket' },
];
Arr.except(goats, ['*.sport']);
/**
[
{ name: 'Saina Nehwal' },
{ name: 'Sunil Chetri' },
{ name: 'Rohit Sharma' },
{ name: 'Virat Kohli' }
]
*/
Read more - Helpers Doc
Internationalisation
If you would like integrate localisation in your application, you can easily do so by just defining your lang_code.json
file inside resources/lang
directory.
Now you can request for translated strings using __
helper.
import { __ } from "@intentjs/core";
__("quote");
// If your dreams do not scare you, they are already becoming a reality.
__("quote", "hi");
// अगर आपके सपने आपको नहीं डरा रहे हैं, तो वो पहले से पुरे होने लग चुके हैं।
Localisation also supports parameterisation, pluralisation out of the box, you can read more about it here.
Task Scheduling
In the past, you may have written individual cron entries on your server for every task you needed to schedule — whether it was sending emails, syncing data, or cleaning up old files. Over time, this becomes hard to manage: your task schedule lives outside your codebase, you lose visibility into what's running and when, and you end up SSHing into your server just to check or update a job.
IntentJS offers a modern, code-first approach to scheduling tasks in your JavaScript or TypeScript apps.
Instead of scattering cron logic across servers or scripts, you define all your recurring tasks in one place — inside your project — using a simple, fluent API. With IntentJS, your entire schedule is part of your source control, versioned alongside your code, and easy to reason about.
import { Schedule } from '@intentjs/core/schedule';
// Run the command every day at midnight...
Schedule.command('users:delete-inactive --force')
.purpose('Delete the users inactive for last 30 days')
.daily()
.run();
// Run once per week on Monday at 1 PM...
Schedule.command('foo')
.weekly()
.mondays()
.at('13:00')
.run();
Support
Though I am currently using Intent in multiple products, it's still the in development, so honestly there will be a few bugs and improvements.
If you run into any similar issues, you can either raise an issue here, or drop me an email at hi@tryintent.com
for any support.
If you like my work, you can follow me on X