<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[vinayak]]></title><description><![CDATA[vinayak]]></description><link>https://vinayak.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 05:28:23 GMT</lastBuildDate><atom:link href="https://vinayak.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Introducing IntentJS - A delightful NodeJS Framework]]></title><description><![CDATA[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 serv...]]></description><link>https://vinayak.hashnode.dev/introducing-intentjs-a-delightful-nodejs-framework</link><guid isPermaLink="true">https://vinayak.hashnode.dev/introducing-intentjs-a-delightful-nodejs-framework</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Vinayak Sarawagi]]></dc:creator><pubDate>Mon, 21 Apr 2025 03:26:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745205748053/646604bb-685a-49fe-9766-bcd5c0446b9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-paradox-of-choice-in-nodejs">The Paradox of Choice in NodeJS</h2>
<p>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.</p>
<blockquote>
<p>Simplicity is the ultimate sophistication</p>
</blockquote>
<p>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.</p>
<h2 id="heading-the-need-for-consistency">The Need for Consistency</h2>
<p>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:</p>
<ul>
<li><p>Switch contexts quickly</p>
</li>
<li><p>Debug efficiently</p>
</li>
<li><p>Implement features rapidly</p>
</li>
</ul>
<h2 id="heading-learning-from-other-ecosystems">Learning from Other Ecosystems</h2>
<p>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 <a target="_blank" href="https://x.com/taylorotwell/status/1791468060903096422">tweet</a> from Taylor Otwell, I began to envision a similar solution for the NodeJS ecosystem.</p>
<h2 id="heading-the-birth-of-intent">The Birth of Intent</h2>
<p>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.</p>
<h2 id="heading-introduction">Introduction</h2>
<p>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.</p>
<p>Some of the features that Intent supports out of the box.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Integration</td><td>Drivers</td></tr>
</thead>
<tbody>
<tr>
<td>Storage</td><td>Unix File Systems, S3</td></tr>
<tr>
<td>Message Queues</td><td>AWS SQS, Redis</td></tr>
<tr>
<td>Mailers</td><td>SMTP, Mailgun, Resend</td></tr>
<tr>
<td>Caching</td><td>Redis, In-Memory</td></tr>
<tr>
<td>Console Commands</td><td></td></tr>
<tr>
<td>Logging</td><td>File Based Logging</td></tr>
<tr>
<td>Exception Handler</td><td>Sentry Integration</td></tr>
<tr>
<td>Task Scheduling</td><td></td></tr>
<tr>
<td>Validations</td><td></td></tr>
<tr>
<td>Transformers</td><td></td></tr>
<tr>
<td>Helpers</td><td>Number, Array, Objects and Strings</td></tr>
<tr>
<td>Internationalisation</td></tr>
</tbody>
</table>
</div><p>Let's take a quick look at how quickly you can use these integrations.</p>
<h2 id="heading-using-storage">Using Storage</h2>
<p>Intent supports <code>UNIX File System</code> and <code>AWS S3</code> right now. All you need to change is the configuration, and done. No change at the code level is needed.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// reads a file</span>
<span class="hljs-keyword">await</span> Storage.disk(<span class="hljs-string">"invoices"</span>).get(<span class="hljs-string">"order_1234.pdf"</span>);

<span class="hljs-comment">// uploads a file</span>
<span class="hljs-keyword">await</span> Storage.disk(<span class="hljs-string">"invoices"</span>).put(<span class="hljs-string">"order_23456.pdf"</span>, content, { 
  mimeType: <span class="hljs-string">"application/pdf"</span> 
});
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/file-storage">Storage Docs</a></p>
<h2 id="heading-message-queues">Message Queues</h2>
<p>For any async tasks, Intent provides support for Redis-based, AWS SQS message queues. Let's take a quick look.</p>
<p>First, define a job with <code>Job</code> decorator.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { Job } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> NotificationJob {
  <span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) {}

  <span class="hljs-meta">@Job</span>(<span class="hljs-string">'user_signedup'</span>)
  <span class="hljs-keyword">async</span> create(data: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">any</span>&gt;) {
    <span class="hljs-comment">// write your logic here</span>
  }
}
</code></pre>
<p>Now, we need to dispatch this job to the queue.</p>
<pre><code class="lang-ts">Dispatch({
  job: <span class="hljs-string">"user_signedup"</span>,
  data: { email: <span class="hljs-string">"vinayak@tryintent.com"</span>, subject: <span class="hljs-string">"Thanks for signing up."</span>, },
});
</code></pre>
<p>Now you can start consuming the messages using <code>node intent queue:work</code> command in terminal.</p>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/queues">Queue Docs</a></p>
<h2 id="heading-mailers">Mailers</h2>
<p>Intent comes with an in-built email template which you can use to build emails. For example,</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { MailMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

<span class="hljs-keyword">const</span> mail = MailMessage.init()
  .greeting(<span class="hljs-string">'Hey there'</span>)
  .line(
    <span class="hljs-string">'We received your request to reset your account password.'</span>,
  )
  .button(<span class="hljs-string">'Click here to reset your password'</span>, <span class="hljs-string">'https://google.com'</span>)
  .line(<span class="hljs-string">'Alternative, you can also enter the code below when prompted'</span>)
  .inlineCode(<span class="hljs-string">'ABCD1234'</span>)
  .line(<span class="hljs-string">'Rise &amp; Shine,'</span>)
  .line(<span class="hljs-string">'V'</span>)
  .subject(<span class="hljs-string">'Hey there from Intent'</span>)
</code></pre>
<p>Above code will output the following email,</p>
<p><img src="https://tryintent.com/docs/email-sample-1.png" alt="Mail Sample" /></p>
<p>Now you can simply send the email with any of the support drivers (Resend, Mailgun, SMTP).</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Mail } <span class="hljs-keyword">from</span> <span class="hljs-string">"@intentjs/core"</span>;

Mail.init()
  .to(<span class="hljs-string">"vinayak@tryintent.com"</span>) <span class="hljs-comment">// OR .to(['id1@email.com', 'id2@email.com'])</span>
  .send(mail);
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/mailers">Mail Docs</a></p>
<h2 id="heading-caching">Caching</h2>
<p>Intent comes out of the support for <code>Redis</code> and <code>In Memory</code> cache database. Let's see how quickly you can start using cache.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// setting value in cache</span>
<span class="hljs-keyword">await</span> Cache.store().set(<span class="hljs-string">"otp"</span>, <span class="hljs-number">1234</span>);

<span class="hljs-comment">// getting value from cache</span>
<span class="hljs-keyword">await</span> CacheStore().get(<span class="hljs-string">"otp"</span>);
</code></pre>
<p>You might also run into situations where the data doesn't exist in the cache, then you can use the <code>remember</code> or <code>rememberForever</code> method.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> cb = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// your custom logic here, for eg. a db query, an api call.</span>
  <span class="hljs-keyword">return</span> [
    { name: <span class="hljs-string">'Shoe Dog'</span>, author: <span class="hljs-string">'Phil Knight'</span>, },
  ];
};

<span class="hljs-keyword">await</span> CacheStore().remember(<span class="hljs-string">"books"</span>, cb, <span class="hljs-number">120</span>);
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/cache">Cache Docs</a></p>
<h2 id="heading-console-commands">Console Commands</h2>
<p>You can also write elegant console commands using Intent which you can run in your terminal using <code>node intent command_name</code>.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Injectable } <span class="hljs-keyword">from</span> <span class="hljs-string">"@nestjs/common"</span>;
<span class="hljs-keyword">import</span> { Command, ConsoleIO } <span class="hljs-keyword">from</span> <span class="hljs-string">"@intentjs/core"</span>;

<span class="hljs-meta">@Injectable</span>()
<span class="hljs-meta">@Command</span>(<span class="hljs-string">"hello {name=world}"</span>, { desc: <span class="hljs-string">"Test Command"</span> })
<span class="hljs-keyword">export</span> <span class="hljs-keyword">class</span> HelloWorldCommand {
  <span class="hljs-keyword">async</span> handle(_cli: ConsoleIO): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt; {
    <span class="hljs-keyword">const</span> name = _cli.argument&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">"name"</span>);
    _cli.info(<span class="hljs-string">`Hello <span class="hljs-subst">${name}</span>!`</span>);
    <span class="hljs-keyword">return</span>;
  }
}
</code></pre>
<p>Now you can run the command in your terminal</p>
<pre><code class="lang-bash">node intent hello vinayak
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/console">Console Docs</a></p>
<h2 id="heading-logging">Logging</h2>
<p>Intent comes with support for file based logging, You can make use of the file based logging. Let's take a quick look,</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Log } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

<span class="hljs-keyword">const</span> logger = Log();

logger.debug(<span class="hljs-string">'hello world!'</span>);
logger.verbose(<span class="hljs-string">'verbose'</span>);
logger.info(<span class="hljs-string">'info'</span>);
logger.warn(<span class="hljs-string">'warn'</span>);
logger.error(<span class="hljs-string">'error'</span>, e);
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/logging">Logging Docs</a></p>
<h2 id="heading-exception-handler">Exception Handler</h2>
<p>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.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { AppConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'@libs/intent'</span>;
<span class="hljs-keyword">import</span> { registerAs } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/config'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> registerAs(
  <span class="hljs-string">'app'</span>,
  <span class="hljs-function">() =&gt;</span>
    ({
      <span class="hljs-comment">// other config here.</span>

      sentry: {
        dsn: process.env.SENTRY_DSN,
        tracesSampleRate: <span class="hljs-number">1.0</span>,
        profilesSampleRate: <span class="hljs-number">1.0</span>,
        integrateNodeProfile: <span class="hljs-literal">true</span>,
      },
    }) <span class="hljs-keyword">as</span> AppConfig,
);
</code></pre>
<p>Read More - <a target="_blank" href="https://tryintent.com/docs/error-handling">Error Handling</a></p>
<h2 id="heading-transformers">Transformers</h2>
<p>Intent comes with support for <code>Transformers</code> 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.</p>
<p>You can read more about it <a target="_blank" href="https://tryintent.com/docs/transformers">here</a>.</p>
<h2 id="heading-helpers">Helpers</h2>
<p>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.</p>
<p>For example, let's take a look at <code>Numbers</code> helper</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Num } <span class="hljs-keyword">from</span> <span class="hljs-string">"@intentjs/core"</span>;
Num.abbreviate(<span class="hljs-number">1200</span>, { precision: <span class="hljs-number">2</span> });
<span class="hljs-comment">// 1.2K</span>

Num.abbreviate(<span class="hljs-number">1200</span>, { locale: <span class="hljs-string">"hi"</span> });
<span class="hljs-comment">// 1.2 हज़ार</span>
</code></pre>
<p>It also has <code>String</code> helpers, for example.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Str } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

Str.pluralize(<span class="hljs-string">'child'</span>);
<span class="hljs-comment">// children</span>

Str.remove(<span class="hljs-string">"New OSS NodeJS Framework"</span>, <span class="hljs-string">"OSS "</span>);
<span class="hljs-comment">// New NodeJS Framework</span>
</code></pre>
<p>If you want to pull some keys from nested <code>objects</code>, you can do so</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Obj } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

<span class="hljs-keyword">const</span> obj = {
  firstName: <span class="hljs-string">"Vinayak"</span>,
  lastName: <span class="hljs-string">"Sarawagi"</span>,
  email: <span class="hljs-string">"vinayak@tryintent.com"</span>,
  wishlist: [
    { id: <span class="hljs-number">1</span>, name: <span class="hljs-string">"Product 1"</span> },
    { id: <span class="hljs-number">2</span>, name: <span class="hljs-string">"Product 2"</span> },
  ],
};

Obj.pick(obj, [<span class="hljs-string">"firstName"</span>, <span class="hljs-string">"lastName"</span>, <span class="hljs-string">"wishlist.*.id"</span>]);
<span class="hljs-comment">/**
 *  {
 *    firstName: 'Vinayak',
 *    lastName: 'Sarawagi',
 *    wishlist: [ { id: 1 }, { id: 2 } ]
 *  }
 */</span>
</code></pre>
<p>Similarly, you can also use <code>Array</code> helpers, let's say you want to return the array but without some keys.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Arr } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core'</span>;

<span class="hljs-keyword">const</span> goats = [
  { name: <span class="hljs-string">'Saina Nehwal'</span>, sport: <span class="hljs-string">'Badminton'</span> },
  { name: <span class="hljs-string">'Sunil Chetri'</span>, sport: <span class="hljs-string">'Football'</span> },
  { name: <span class="hljs-string">'Rohit Sharma'</span>, sport: <span class="hljs-string">'Cricket'</span> },
  { name: <span class="hljs-string">'Virat Kohli'</span>, sport: <span class="hljs-string">'Cricket'</span> },
];

Arr.except(goats, [<span class="hljs-string">'*.sport'</span>]);
<span class="hljs-comment">/**
  [
    { name: 'Saina Nehwal' },
    { name: 'Sunil Chetri' },
    { name: 'Rohit Sharma' },
    { name: 'Virat Kohli' }
  ]
*/</span>
</code></pre>
<p>Read more - <a target="_blank" href="https://tryintent.com/">Helpers Doc</a></p>
<h2 id="heading-internationalisation">Internationalisation</h2>
<p>If you would like integrate localisation in your application, you can easily do so by just defining your <code>lang_code.json</code> file inside <code>resources/lang</code> directory.</p>
<p>Now you can request for translated strings using <code>__</code> helper.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { __ } <span class="hljs-keyword">from</span> <span class="hljs-string">"@intentjs/core"</span>;

__(<span class="hljs-string">"quote"</span>);
<span class="hljs-comment">// If your dreams do not scare you, they are already becoming a reality.</span>

__(<span class="hljs-string">"quote"</span>, <span class="hljs-string">"hi"</span>);
<span class="hljs-comment">// अगर आपके सपने आपको नहीं डरा रहे हैं, तो वो पहले से पुरे होने लग चुके हैं।</span>
</code></pre>
<p>Localisation also supports parameterisation, pluralisation out of the box, you can read more about it <a target="_blank" href="https://tryintent.com/docs/localization">here</a>.</p>
<h2 id="heading-task-scheduling">Task Scheduling</h2>
<p>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.</p>
<p>IntentJS offers a modern, code-first approach to scheduling tasks in your JavaScript or TypeScript apps.</p>
<p>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.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Schedule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@intentjs/core/schedule'</span>;

<span class="hljs-comment">// Run the command every day at midnight...</span>
Schedule.command(<span class="hljs-string">'users:delete-inactive --force'</span>)
  .purpose(<span class="hljs-string">'Delete the users inactive for last 30 days'</span>)
  .daily()
  .run();

<span class="hljs-comment">// Run once per week on Monday at 1 PM...</span>
Schedule.command(<span class="hljs-string">'foo'</span>)
  .weekly()
  .mondays()
  .at(<span class="hljs-string">'13:00'</span>)
  .run();
</code></pre>
<h2 id="heading-support">Support</h2>
<p>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 <a target="_blank" href="https://github.com/intentjs/core/issues/new?assignees=&amp;labels=&amp;projects=&amp;template=bug_report.md&amp;title=">here</a>, or drop me an email at <a target="_blank" href="mailto:hi@tryintent.com"><code>hi@tryintent.com</code></a> for any support.</p>
<p>If you like my work, you can follow me on <a target="_blank" href="https://x.com/vinayak2506">X</a></p>
]]></content:encoded></item></channel></rss>