Hero

Daksh Jain • Technical Writing

Insights on React, Next.js & Scalable Full Stack Architecture

I share practical insights from building real-world applications using Node.js, PostgreSQL, Redis, and modern frontend frameworks. These articles document my experience with performance optimization, backend APIs, system design, and production-ready architecture.

My Latest Articles

I share insights about web development, React, Next.js and building scalable applications.

8/19/2026

I Got Tired of Setting Up Node.js Projects Manually, So I Built My First NPM Package

If you have worked with Node.js for some time, you probably know this feeling.

You create a new backend project.

Then you start doing the same things again.

Initialize npm.

Install Express.

Create the server.

Create app.js.

Create routes.

Create controllers.

Create services.

Add error handling.

Add environment configuration.

Add database setup.

And finally, create the folder structure you use in almost every project.

Nothing here is difficult.

But doing the same thing again and again?

That becomes boring.

So I started thinking:

“Why am I creating the same Node.js setup manually every time?”

And that question became the beginning of my first NPM package.

The Idea

I wanted something simple.

Something where I could run one command:

npx @jain_daksh/nodecli my-backend

And instead of spending the next 10 minutes creating files and folders manually,

I wanted the project to be ready with my preferred backend structure.

Something like:

my-backend/

├── src/
│ ├── controllers/
│ ├── services/
│ ├── routes/
│ ├── models/
│ ├── serializers/
│ ├── validations/
│ ├── utils/
│ └── seed/

├── tests/

├── .env
├── .gitignore
├── package.json
└── src/app.js

The idea was not to create another Express tutorial.

The idea was:

Automate the boring part of starting a Node.js project.

And I decided to call it:

NodeCli

The name felt right.

A small tool that helps forge the initial structure of a Node.js backend.

What I Wanted NodeCli To Do

I wanted the CLI to handle the initial setup for me.

So the workflow would be something like:

Create project

Choose JavaScript / TypeScript

Create folder structure

Create configuration files

Create app/server setup

Create routes/controllers/services

Install required dependencies

Ready to start coding

Instead of manually creating everything,

the CLI would do it.

The First Challenge

The first question was:

“How do I actually create an NPM CLI?”

I had used many NPM packages before.

But using a package and building one are completely different things.

I started looking at how CLI packages work.

The important part was the bin field in package.json.

Something like:

{
"name": "@jain_daksh/nodecli",
"version": "1.0.0",
"bin": {
"nodeCli": "./bin/cli.js"
}
}

This basically tells NPM:

“When someone runs nodecli, execute this file.”

That was the first important piece.

Creating The CLI

I created a bin folder.

Inside it:

bin/
└── cli.js

This became the entry point of NodeCli.

The CLI would start from here.

Then I added prompts so the user could choose the type of project.

For example:

What type of project do you want?
❯ TypeScript
JavaScript

I used @inquirer/prompts for this.

Now the CLI could actually interact with the developer.

JavaScript And TypeScript

I didn’t want NodeCli to support only one language.

Because depending on the project, I might want:

JavaScript

or:

TypeScript

So I created separate templates.

Something like:

templates/

├── javascript/
│ ├── src/
│ ├── package.json
│ └── ...

└── typescript/
├── src/
├── package.json
└── ...

The CLI would ask the developer which one they wanted.

Then it would generate the appropriate files.

The Backend Structure

I wanted the generated project to follow the structure I normally use.

For example:

src/

├── controllers/
├── services/
├── routes/
├── models/
├── serializers/
├── validations/
├── utils/
└── seed/

The idea is simple.

Each part has its own responsibility.

Controllers handle requests.

Services contain business logic.

Routes define API endpoints.

Models handle database-related logic.

Validations handle input validation.

Serializers handle response formatting.

Utils contain reusable functionality.

This means when I start a new backend project,

I don’t have to think:

“Where should I create this file?”

The structure is already there.

Creating Files Automatically

This was probably the most interesting part.

Instead of manually creating:

controllers/
services/
routes/
models/

NodeCli creates them programmatically.

The CLI checks the selected template and copies the required files into the new project.

So when I run:

npx @jain_daksh/nodecli my-api

NodeCli creates:

my-api/

and then builds the complete structure inside it.

This is where the package started feeling useful.

It wasn’t just a collection of files anymore.

It was actually automating my workflow.

Adding Default Files

I also wanted some files to be available from the beginning.

For example:

app.js
server.js
.env
.gitignore

And some reusable utility files.

For example, I commonly use API response helpers.

So instead of creating these every time, NodeCli can generate them as part of the initial setup.

The goal was:

Start with a clean foundation instead of starting with an empty folder.

Database Setup

I also wanted the generated project to be ready for database integration.

For example, depending on the project, I could use tools such as:

Sequelize
Prisma
PostgreSQL

So I started thinking about the project structure in a way that would allow database setup to be extended later.

This is one thing I learned while building NodeCli:

A CLI becomes much more useful when it is designed around real development workflows instead of just generating boilerplate files.

Testing It Locally

After building the first version,

I didn’t immediately publish it.

First, I wanted to test whether the CLI actually worked.

I created a test project.

Then ran the CLI.

And checked whether the expected structure was generated.

There were small issues.

For example, while testing folder creation, I ran into an error like:

EEXIST: file already exists

The problem was simple.

I was trying to create a directory that already existed.

But this was actually a useful lesson.

When you manually create a project, you control every step.

When you create a CLI that creates projects for other developers,

you need to think about things like:

“What happens if the folder already exists?”
“What if the user doesn’t provide a project name?”
“What if the user runs the command inside an existing project?”
“What if a file already exists?”

These small edge cases become important.

Default Project Name

I also wanted the CLI to work even when the developer didn’t provide a project name.

So instead of requiring:

npx @jain_daksh/nodecli backend

I wanted this to work too:

npx jain_daksh/nodecli

And if no name was provided,

the default project name would be:

backend

Small feature.

But useful.

Finally Publishing To NPM

After testing everything,

it was time for the part I had never done before.

Publishing my own package.

I created the package metadata:

{
"name": "@jain_daksh/nodecli",
"version": "1.0.0"
}

Then I logged into NPM.

And finally:

npm publish --access public

And then I saw it.

My package was published.

My first NPM package.

That feeling was different.

Because this wasn’t just code sitting inside my GitHub repository.

It was now something that another developer could install.

Using My Own Package

The final test was the simplest one.

I opened a new terminal.

And ran:

npx @jain_daksh/nodecli

The CLI started.

The prompt appeared.

I selected JavaScript or TypeScript.

And the project was generated.

That was the moment I thought:

“Okay, I actually built an NPM package.”

What I Learned

Building NodeCli taught me something important.

Creating an NPM package isn’t only about writing JavaScript.

There are many other things involved.

You have to think about:

  • Package structure
  • CLI entry points
  • NPM configuration
  • File generation
  • User interaction
  • Error handling
  • Existing directories
  • Default values
  • Versioning
  • Publishing
  • Testing

And most importantly,

you have to think about how another developer will use your tool.

What’s Next For NodeCli?

Version 1.0.0 is only the beginning.

There are many things I want to improve.

For example:

More database templates
More configuration options
Better error handling
Authentication boilerplate
Docker setup
Redis setup
Testing setup
Environment configuration
More customizable project structures

Eventually, I want NodeCli to become more than just:

“Create some folders for me.”

I want it to become:

“Give me a production-ready starting point for my Node.js backend.”

Final Thought

I started this project because I was tired of doing the same setup repeatedly.

But while building it,

I realized something.

Sometimes the best projects don’t start with a huge idea.

They start with a small annoyance.

You do something repeatedly.

You think:

“There has to be a better way.”

And instead of searching for a solution,

you build one.

NodeCli is my first NPM package.

It may be small right now.

But it taught me how an idea can go from:

“I'm tired of doing this manually.”

to:

npx @jain_daksh/nodecli

And honestly,

that’s probably my favorite part of building software.

4/10/2026

SQL JOINs Explained Like You’re Exploring a Perfectly Organized Library

Databases for Beginners (Part 2): How Data From Different Tables Connect

Imagine you’re still inside that massive library.

Everything is organized perfectly.

But this time, something feels different.

The Problem

Books are stored in one section.

Authors are stored in another.

Now someone walks up to you and asks:

“Show me all books along with their authors.”

You pause.

Because the data is not in one place anymore.

This is where things change

You now have to connect information from multiple sections.

So you walk between shelves, match records, and combine them.

That process?

That’s called a JOIN.
INNER JOIN — “Only matching data”

You decide:

“Only show results where both book AND author exist.”

If a book doesn’t have an author entry → ignore it

If an author isn’t linked to a book → ignore them

Only perfect matches.

SELECT b.title, a.name
FROM books b
INNER JOIN authors a
ON b.author_id = a.id;

Real-world example:

Think about ordering food.

You only see:

dishes that exist

AND restaurants that serve them

No match = nothing shown

INNER JOIN = only matching data
LEFT JOIN — “Show everything from the left”

Now you change your approach:

“Show ALL books, even if author info is missing.”

So:

Every book appears

If author exists → show it

If not → show NULL

SELECT b.title, a.name
FROM books b
LEFT JOIN authors a
ON b.author_id = a.id;

Real-world example:

Think about a classroom list.

All students are listed

Some submitted assignments

Some didn’t

Missing data shows as empty

LEFT JOIN = everything on left + matches
RIGHT JOIN = “Show everything from the right”

Now flip it.

“Show all authors even if they have no books.”

SELECT b.title, a.name
FROM books b
RIGHT JOIN authors a
ON b.author_id = a.id;

Real-world example:

Think of a company:

All employees listed

Some haven’t been assigned projects yet

Still included

RIGHT JOIN = everything on right + matches
FULL OUTER JOIN — “Show everything”

Now you go all in:

“I want EVERYTHING books and authors no matter what.”

SELECT b.title, a.name
FROM books b
FULL OUTER JOIN authors a
ON b.author_id = a.id;

What you get:

matched records

books without authors

authors without books

FULL JOIN = everything combined

Think of it simply

JOIN Type Meaning

INNER Only matches

LEFT All left + matches

RIGHT All right + matches

FULL Everything

One simple trick to remember

Think of JOIN like a filter:

INNER → strict
LEFT → slightly relaxed
RIGHT → flipped version
FULL → no filtering

Final Thought

JOIN is not complicated.

It’s just answering one question:

“What data belongs together?”

Once you understand that,

Databases stop feeling like tables.

And start feeling like connected systems.

4/8/2026

Databases Explained for Beginners: The Simple Story Behind Every Click You Make

Databases for Beginners: A Story of Asking, Changing, and Managing Data

Imagine you walk into a huge library.

Not a normal one like the one near your house…

This one has millions of books, perfectly organized.

Every book has:

an ID

a title

an author

a category

This library is your database.

And you?

You are the librarian in charge.

You’re the one constantly asking things like:

“Show me all books.”

“Add this new book.”

“Update this one.”

“Delete that old one.”

In the database world, these actions have names.

Let’s turn this into a conversation.

SELECT — “Show me what you have”

You walk to the librarian and say:

“Can you show me all the books?”

The librarian smiles and hands you a list.

That’s SELECT.

SELECT * FROM books;

You can be more specific:

“Only show me books by Harry Potter.”

SELECT * FROM books WHERE author = ‘J.K. Rowling’;

Real-world example:

Think about Instagram.

When you open your feed, the app is basically saying:

“Show me all posts from people this user follows.”

That’s a SELECT query running behind the scenes.

👉 SELECT is simply asking questions.

INSERT — “Add this to your system”

Now you bring a new book.

“Hey, add this to your library.”

The librarian takes it, assigns an ID, and stores it.

That’s INSERT.

INSERT INTO books (title, author)
VALUES (‘Atomic Habits’, ‘James Clear’);

Real-world example:

When you create a new account or post a photo:

“Add this new user or post to the database.”

That’s INSERT.

👉 INSERT is adding new data.

UPDATE — “Change something that already exists”

You notice a mistake.

“This book has the wrong author. Fix it.”

The librarian updates the record.

That’s UPDATE.

UPDATE books
SET author = ‘Correct Author’
WHERE id = 1;

Real-world example:

When you edit your profile:

Change username

Update bio

Modify profile picture

That’s UPDATE.

👉 UPDATE is editing existing data.
⚠️ Without WHERE, you update everything.

That’s like rewriting every book in the library by mistake 😬

DELETE — “Remove this completely”

Now you say:

“This book shouldn’t exist anymore.”

The librarian removes it.

That’s DELETE.

DELETE FROM books WHERE id = 1;

Real-world example:

When you delete a post or account:

That data is removed from the database.

That’s DELETE.

👉 DELETE is removing data permanently.
⚠️ Again… no WHERE = disaster 💀

JOIN — “Connect different pieces of information”

Now things get interesting.

You ask:

“Show me all books and their authors’ details.”

But wait…

Authors are stored in a different section.

The librarian now combines information from two places.

That’s a JOIN.

(Coming soon in Part 2 👀)

Think of it like real life

Action Real Life Meaning

SELECT Ask a question

INSERT Add something

UPDATE Fix or change something

DELETE Remove something

Final Thought

Every app you use — Instagram, Amazon, Netflix —

is constantly running these operations behind the scenes:

Fetching data (SELECT)

Adding new data (INSERT)

Updating data (UPDATE)

Deleting data (DELETE)

That’s it.

Not magic.

Just structured conversations with a database happening every second.

2/12/2026

Authentication vs Authorization in Backend: Who Gets Into Avengers Tower?

Imagine you’re standing outside Avengers Tower.

Five people arrive:

  • Tony Stark
  • Steve Rogers
  • Peter Parker
  • Bruce Wayne (oops, wrong universe 👀)
  • Nick Fury

Security activates.

Step 1: Authentication -“Who are you?”

Before anyone enters, the system asks:

“Prove your identity.”

Tony scans his face — verified.
Steve enters credentials — verified.
Peter logs in — verified.
Nick Fury passes retina scan — verified.
Bruce Wayne? System error. Wrong database.

Bruce fails authentication.

Authentication is about identity verification.

In backend systems, this usually means:

  • Verifying email & password
  • Comparing hashed passwords (bcrypt / argon2)
  • Validating JWT tokens
  • Checking server-side sessions
  • OAuth login (Google, GitHub, etc.)

If authentication fails → return 401 Unauthorized.

⚠️ Important:
401 actually means “Unauthenticated” — you haven’t proven who you are yet.

No identity → no entry.

Step 2: Authorization-“What are you allowed to access?”

Now four people are inside.

But not everyone has the same clearance.

  • Tony can access the lab.
  • Steve can enter mission control.
  • Peter can use training floors.

But only Nick Fury can access the Main Helipad Control Room.

That’s authorization.

The system already knows who you are.
Now it checks your permissions or role.

If permission fails → return 403 Forbidden.
You are authenticated.
But you are not allowed.

Let’s Build an Avengers Tower API

Authentication Middleware (JWT Verification)

const jwt = require("jsonwebtoken");

function authenticate(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];

if (!token) {
return res.status(401).json({ message: "Authentication required" });
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ message: "Invalid or expired token" });
}
}
If the token is missing or invalid → Bruce Wayne stays outside.

Authorization Middleware (Role-Based Access Control)

function authorize(...allowedRoles) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ message: "Access denied" });
}
next();
};
}

Now protect the helipad route:

app.get(
"/helipad",
authenticate,
authorize("director"),
(req, res) => {
res.json({ message: "Welcome to the Helipad Control Room" });
}
);
Only users with role "director" (Nick Fury) can access it.
Tony? Denied.
Steve? Denied.
Peter? Definitely denied.

Real-World Architecture Flow

In production systems, this typically looks like:

  1. User logs in
  2. Server verifies credentials
  3. Server generates a JWT containing:
  • userId
  • role
  • permissions

4. Client stores token (preferably HTTP-only cookie)

5. Each request includes token

6. Backend verifies token → Authentication

7. Backend checks role/permissions → Authorization

But is this enough?

Big No lets look at Security Best Practices

If you’re building real systems:

  • Never store plain passwords → always hash (bcrypt / argon2)
  • Avoid storing JWT in localStorage in production apps (XSS risk)
  • Use HTTP-only cookies for tokens
  • Use short-lived access tokens
  • Implement refresh tokens properly
  • Avoid sensitive data inside JWT payload
  • Keep authentication and authorization logic separate

Let’s end this now with a Final Difference (Once and Forever)

Authentication → Who are you?
Authorization → What are you allowed to do?

Bruce Wayne = Failed authentication
Tony Stark = Authenticated but unauthorized
Nick Fury = Fully authorized

And just like in real backend systems…

Not everyone gets access to the helipad.

12/3/2024

HTTP Status Codes: A Love Story Between You and the Server

Imagine you’re texting someone, asking them for something. You send a message, they read it, and then they reply. Sometimes, they reply with excitement, other times with confusion or disappointment. In the world of the internet, those “replies” are called HTTP status codes. They’re how the server responds to your requests when you’re browsing the web. Think of it like a conversation between you (the user) and the server (the one that stores all the info you want to access).

Here’s a fun way to understand what these codes mean — as if your request is a love note and the server is either accepting, rejecting, or responding in a different way. Ready?

200: Success — “You’ve got it!”
This is the ultimate “YES!” The server reads your request and thinks, “Yep, that makes sense. I can do that for you.” Your request has been accepted, and everything is working perfectly. It’s like when you ask someone to go to the movies, and they’re like, “Sure, let’s go!” It’s smooth sailing.

400: Bad Request — “Huh? What do you mean?”
This is the moment when you ask something, and the server’s like, “I’m not sure what you’re talking about.” Maybe you’ve made a mistake in how you phrased it — like sending a message with random letters or numbers. The server’s confused and can’t understand you. It’s not rejecting you; it just needs you to be clearer.

401: Unauthorized — “Sorry, you’re not allowed.”
You try to make your request, but the server says, “Whoa, hold up. I don’t know you, and you can’t just get in here without the right password.” It’s like showing up at a party, but you’re not on the guest list. The server doesn’t trust you yet, and you need the proper credentials (like a password or permission) to get through.

403: Forbidden — “No entry for you.”
This is more of a harsh “no” than a “not now.” The server knows exactly who you are and what you want, but it still won’t let you in. Maybe you’re asking for something you shouldn’t have access to, or it’s just a flat-out refusal. It’s like when you try to enter a VIP section, but they tell you, “Sorry, you can’t come in.”

404: Not Found — “I can’t find that.”
You asked for something, but the server’s like, “I don’t know what you’re talking about. It’s not here.” It’s as if you’re texting someone and asking for a place that doesn’t exist. The server either doesn’t have the resource, or it’s not available at the moment. This is probably the most common response when you misspell a URL or click on a broken link.

422: Unprocessable Entity — “I get it, but I can’t do that.”
You’ve asked for something, and the server says, “I understand you, but I can’t make it happen right now.” You didn’t make any mistakes, but the request is still too complicated or not quite right for the server to process. It’s like you ask someone to do something nice, but they say, “I can’t do that today, but I get what you’re asking.”

500: Internal Server Error — “Something went wrong on my end.”
This is when everything seems fine, and then — oops! The server just collapses, like a sudden breakdown. You made a perfect request, but the server’s having a bad day. It’s like texting someone, and they say, “Sorry, I don’t know what happened, but I’m not able to do that right now.” The problem is on the server’s side, and they’re trying to fix it.

So, every time you make a request online, you’re actually entering a little dance with the server. Sometimes, the response is positive — everything goes great. Other times, you hit a wall, or the server needs you to try again, in a different way. It’s all part of the connection.

And just like in any relationship, sometimes things go smoothly, sometimes there are misunderstandings. But don’t worry — the dance continues, and there’s always another chance for a successful request.

© 2026 Daksh Jain— All rights reserved