# Ayush Chugh

> Full stack web developer from Chandigarh, India. Team Lead at Avenue Ticketing. This site is his portfolio, story, resume, projects, and blog on web engineering and systems.

Ayush Chugh writes production web software with React, Next.js, TypeScript, and Node.js, and has shipped on PostgreSQL, MongoDB, AWS, Docker, Railway, and Stripe. He started coding in 2020, started professional work in 2021, and is available for freelance. He takes ownership of the product, not only the code, and uses AI with review and architectural oversight.

This file is the map. Work, projects, education, and volunteering repeat the facts on the site. The blog section lists every post, then includes the full article text so you can quote it without rendering the HTML pages.

## Pages

- [Home](https://ayushchugh.com/): Full stack web developer in Chandigarh, India. Team Lead at Avenue Ticketing (team of 7) building a ticketing platform. Co-founder and CTO at Ravix Studio from Nov 2025 to March 2026; that startup is paused. Previously built a B2B flight booking platform at Vibranium Soft and internal tools at Tap Invest. Available for freelance. Stack: React, Next.js, TypeScript, Node.js, PostgreSQL, AWS. Email hey@ayushchugh.com. GitHub aayushchugh.
- [Story](https://ayushchugh.com/story): Grew up in Jalalabad, Fazilka, Punjab, wanted to be an IPS officer, and studied at DAV Public School. Started coding at 13 during COVID lockdown after Turbo C didn't stick and a brother opened VS Code. Learned HTML and CSS in about a week on a second-hand HP laptop after the family moved to Chandigarh in 9th grade. No roadmap: JavaScript, Git, then SCSS, mostly from Stack Overflow, GitHub issues, and Discord. First shipped project was a2infinite (Feb 2021) with a cousin. Later owned Shri Property end to end for family, then worldtravelsonline.in at Vibranium Soft. Joined Avenue Ticketing part-time, became team lead in August 2025. Co-founded Ravix Studio as CTO; that chapter is paused. Still in college (third year), codes after class. Writes to check that he understands something.
- [Projects](https://ayushchugh.com/projects): Freelance and personal work: Worldtravels, Repo Command, an EJS VS Code extension, Invoicen, Shriproperty, A2INFINITE, FlipTimer, and the Sky At Night VS Code theme. Each project is listed below with its stack and URL.
- [Resume](https://ayushchugh.com/resume): Work history from 2021 to present, education, and volunteering. Roles, dates, stacks, and responsibilities are listed in Work, Education, and Volunteering below.
- [Blog](https://ayushchugh.com/blog): Essays on systems, web engineering, and things built in production. Each post below includes the full article text.
- [Community](https://ayushchugh.com/community): Ravix Studio Discord (https://discord.gg/wydHwZNu8W). Direct access to Ayush and other developers, open-source and SaaS collaboration, and project updates.
- [Contact](https://ayushchugh.com/contact): Freelance, collaboration, or jobs. Email hey@ayushchugh.com. Book a 1:1 on Topmate: https://topmate.io/aayushchugh.

## Work

- [Avenue Ticketing, Inc.](https://avenueticket.com/): Team Lead, Aug 2025 - Present, Full-time, Remote. Stack: NodeJS, TypeScript, PostgreSQL, AWS, Stripe, Docker, Railway. Developing complex end-to-end features including payment processing, event management, and user authentication systems Building advanced TypeScript modules with complex business logic for ticket validation, pricing calculations Implementing sophisticated error handling and data validation patterns across the entire application stack Creating reusable components and utilities that handle complex state management and API integrations Identifying and fixing edge cases in the codebase Implementing monitoring and logging solutions for the application Full Stack Developer, Apr 2025 - Aug 2025, Part-time, Remote. Stack: NodeJS, TypeScript, PostgreSQL, AWS, Stripe. Developing and maintaining a ticketing platform for event organizers and attendees. Implementing secure authentication and authorization systems for organizers and users. Optimizing server performance and implementing monitoring solutions for high-traffic events. Re-writing legacy code to improve code quality and maintainability. Creating and maintaining documentation for the codebase and processes.
- [Ravix Studio](https://ravixstudio.com/): Co-founder & CTO, Nov 2025 - March 2026, Full-time, On-site. Stack: NextJS, TypeScript, NodeJS, PostgreSQL, AWS, Docker, Railway. Building alot of cool stuff
- [Vibranium Soft](https://www.linkedin.com/company/vibraniumsoft/posts/?feedView=all): Frontend Developer, December 2023 - July 2025, Freelance, Remote. Stack: NextJS, Redux, TypeScript, Tailwind CSS, MUI. Developing and maintaining a B2B flight booking platform. Collaborating with cross-functional teams to deliver optimized solutions for the travel industry. Implementing features for flight search, booking management, and user onboarding. Building a user-friendly interface to enhance customer experience. Created responsive layouts for different screen sizes and devices. Used pooling and caching strategies to improve performance and reduce load times. Optimized SEO by implementing proper server side rendering
- [Keizer Works](https://keizerworks.com/): SDE, November 2024 - April 2025, Freelance, Remote. Stack: NextJS, TypeScript, Tailwind CSS, NodeJS, PostgreSQL, Shadcn. Actively maintaining and contributing to open-source projects that drive innovation and collaboration within the tech community. Developing scalable and robust applications to meet client needs and deliver impactful solutions. Assisting in managing projects from inception to delivery, ensuring timelines, quality standards, and client expectations are met. Supporting the operational and strategic growth of the startup by streamlining workflows and contributing to decision-making processes. Worked with external clients of the startup and worked on internal products too
- [Tap Invest](https://tapinvest.in/): Frontend Developer, Apr 2024 - Nov 2024, Part-time, Remote. Stack: NextJS, React, TypeScript, MUI, Tailwind CSS. Developed internal dashboard tools for the startup using React and NextJS, improving operational efficiency Translate design concepts into dynamic and responsive web applications. Optimize performance and ensure accessibility to deliver user-centric solutions. Created complex reusable components to speed up development and reduce maintenance costs.
- [Shri Property](https://shriproperty.com): Full-Stack Developer, Dec 2021 - May 2023, Freelance, Remote. Stack: React, Node.js, MongoDB, Express.js, TypeScript. Developed a real estate platform that simplified the process of finding and listing properties. Led the complete development of the website, from UI/UX design to backend integrations. Created a fully-featured admin dashboard for operations and management. Each property is listed after going throw a proper verification process by operations team.
- [A2INFINITE](https://a2infinite.com): Frontend Developer, Feb 2021 - May 2021, Freelance, Remote. Stack: HTML, CSS, JavaScript, SCSS. Developed and maintained a platform that offers downloadable and printable practice worksheets for students. Collaborated with the design and backend teams to ensure smooth platform functionality and user experience.

## Projects

- [Worldtravels (2k+ active users daily)](https://www.worldtravelsonline.in/en): A B2B flight booking platform for travel agencies to manage bookings and payments. I have built the end to end frontend for the platform. Freelance Frontend project. Built with React, Redux, TypeScript, NextJS.
- [Repo Command](https://github.com/marketplace/repo-command): A Github app that lets you automate tasks by adding labels and using `/` commands in comments to approve or merge pull requests. Personal Backend project. Built with Node.js, TypeScript, Probot.
- [EJS Snippets And Color Highlighting for VsCode (7k+ downloads)](https://marketplace.visualstudio.com/items/?itemName=AyushChugh.ejs-snippets-and-color-highlighting): Add snippets and syntax highlighting for EJS Personal Tools project. Built with Javascript, vsce.
- [Invoicen](https://invoicen.ayushchugh.com): A Simple Invoice Generator for Freelancers and Businesses and enterprises. Personal Full Stack project. Built with NextJS, shadcn, TypeScript, Tailwind.
- [Shriproperty](https://www.shriproperty.com): A real estate platform that simplifies the process of finding and listing properties. Freelance Full Stack project. Built with React, Node.js, MongoDB, Express.js.
- [A2INFINITE](https://a2infinite.com): A platform for downloadable practice worksheets for students. Freelance Frontend project. Built with HTML, CSS, JavaScript.
- [FlipTimer](https://fliptimer.ayushchugh.com): A countdown timer that lets users set a target date and displays time left in months, weeks, days, hours, minutes, and seconds. Personal Frontend project. Built with NextJS, TypeScript, shadcn, framer-motion.
- [Sky At Night](https://marketplace.visualstudio.com/items?itemName=AyushChugh.sky-at-night): A dark theme for vscode Personal Tools project. Built with JavaScript, vsce.

## Education

- [Chandigarh Group of Colleges, Landran, Punjab](https://www.cgc.edu.in/): Bachelor of Technology (B.Tech), Computer Science and Engineering. 2025 - 2028. Chandigarh, India.
- [Chandigarh College of Engineering & Technology (Diploma Wing)](https://ayushchugh.com/resume): Diploma, Computer Science and Engineering. 2022 - 2025. Chandigarh, India.
- [Govt Model School Sector 35D Chandigarh India](https://ayushchugh.com/resume): 10th, School. 2021 - 2022. Chandigarh, India.

## Volunteering

- [Hack With India-Chapter CEC-CGC](https://hackwithindia.in/): Technical Lead, Aug 2025 - Present, Chandigarh, India. I am responsible to manage all the technical aspects of the club and help the students to learn and grow in the field of technology.
- [Innovation Club, CCET](https://ayushchugh.com/resume): Team Lead, Oct 2023 - Nov 2024, Chandigarh, India. Leading a team to innovate and implement technology projects in the institution. Organizing hackathons and coding competitions, encouraging student participation and skills development.

## Blog

- [Technical decisions that stick](https://ayushchugh.com/blog/technical-decisions-that-stick): Published 2026-09-13. Chat is bad memory. Temporary fixes become the real system. Write decisions down so they still exist when someone asks why.
- [Client-Server Architecture (How the Web Actually Works)](https://ayushchugh.com/blog/client-server-architecture-how-the-web-actually-works): Published 2026-04-12. A practical walkthrough of client-server architecture: how browsers talk to servers over HTTP, what request/response really means, and why the web is built this way.
- [What is a System? (And How Engineers Think About Systems)](https://ayushchugh.com/blog/what-is-a-system-and-how-enginners-think-about-systems): Published 2026-04-05. A deep dive into the concept of systems and how engineers think about them.
- [You Don’t Need More Tutorials. You Need Engineering Thinking.](https://ayushchugh.com/blog/you-don't-need-more-tutorials-you-need-enginnering-skills): Published 2026-03-22. Tutorials teach syntax and controlled demos, not engineering. This piece defines engineering as building systems that work under real constraints—scale, failure, messy users—and introduces a first-principles series on how systems actually behave, from the network to observability, so you reason instead of follow steps.
- [How I Built In-App Notifications in Avenue](https://ayushchugh.com/blog/how-i-built-in-app-notifications-in-avenue): Published 2026-03-15. A deep dive into building a scalable notification system for Avenue's event ticketing platform: one central service, one source of truth, real-time delivery via WebSocket, and push notifications—all working together to notify organizers and team members at the right time.
- [How I Built a Token-Bucket Rate Limiter for Emails & SMS](https://ayushchugh.com/blog/token-based-rate-limiting-email-sms): Published 2025-11-19. Learn how to implement a token-bucket rate limiter with priority queues to throttle email and SMS sending, preventing provider rate limit violations while ensuring critical messages are sent first.
- [Accepting payments with Tap to Pay using Stripe](https://ayushchugh.com/blog/tap-to-pay-stripe): Published 2025-08-24. A step-by-step guide to implement Tap to Pay with Stripe.
- [The Grand Digital Paradox: Why Indian Government Websites Feel Stuck in Time](https://ayushchugh.com/blog/why-govt-websitesh-have-bad-design): Published 2025-07-17. A deep dive into the design challenges faced by Indian government websites and the user experience issues that arise from them.
- [Hoisting in JavaScript](https://ayushchugh.com/blog/hoisting-in-js): Published 2025-07-13. An overview of hoisting behavior in JavaScript and its implications.
- [IndexedDB: Database inside browser](https://ayushchugh.com/blog/indexed-db): Published 2025-05-19. IndexedDB is a browser-native NoSQL database API for efficiently storing, querying, and managing large volumes of structured data locally—with support for transactions, indexes, and offline-first applications.
- [Mastering view port: 100vh scroll problem](https://ayushchugh.com/blog/mastering-view-ports): Published 2025-05-04. A deep dive into CSS viewport units (vh, vw) and how the new small (sv*), large (lv*), and dynamic (dv*) viewport units solve the annoying 100vh scrolling issue on mobile and dynamic-browser UIs.
- [Progressive Web Apps: Create apps from websites](https://ayushchugh.com/blog/pwa-create-apps-from-websites): Published 2025-01-25. Progressive Web Apps (PWAs) are web applications that are built using web technologies but behave like native apps. They are fast, reliable and engaging.
- [Load Shedding: Scaling down to scale up](https://ayushchugh.com/blog/load-shedding): Published 2024-10-14. Load shedding is a technique to scale down the resources of a system to prevent it from crashing under heavy load.
- [Self hosting Docmost, an open-source alternative to Notion](https://ayushchugh.com/blog/self-hosting-docmost): Published 2024-10-06. A step-by-step guide to deploy Docmost on your VPS and setting up a custom domain.
- [Unlocking Beckn: The Unsung Hero of eCommerce Revolution](https://ayushchugh.com/blog/unlocking-beckn-the-unsung-hero-of-eCommerce-revolution): Published 2024-09-23. Connecting multiple platforms without the need for a central authority.
- [How I started my journey in web development as 13 year old](https://ayushchugh.com/blog/how-i-started-my-journey-in-web-development-as-13-year-old): Published 2024-09-13. A guide to setting up your Next.js project efficiently.
- [Initial Setup for Next.js Projects](https://ayushchugh.com/blog/initial-setup): Published 2024-09-13. A guide to setting up your Next.js project efficiently.

### [Technical decisions that stick](https://ayushchugh.com/blog/technical-decisions-that-stick)

Published: 2026-09-13

Chat is bad memory. Temporary fixes become the real system. Write decisions down so they still exist when someone asks why.

i keep seeing the same pattern.

someone asks a hard question in slack. people reply. someone says "lets do X for now". everyone moves on. two months later nobody remembers why X exists, and the person who said it already switched teams.

the decision happened. it just did not stick.

this is not a process i rolled out anywhere. it is something i have been thinking about while shipping, watching temporary fixes quietly become the real system.

## chat is bad memory

chat is great for speed. it is bad for truth over time.

when the decision lives only in a thread:
1. the context dies when the thread scrolls away
2. new people cannot find why something is weird
3. you reinvent the same debate every quarter

if the only record is "i think we said JWT?" then you do not have a decision. you have a rumor.

## temporary is a lie we tell ourselves

"temp fix" is the most dangerous phrase in engineering.

because temp often means:
- no ticket
- no owner
- no date to remove it
- no note that this was a tradeoff

six months later the temp fix is load bearing. nobody wants to touch it. and now you are scared of the thing you built to reduce fear.

i am not saying never ship a shortcut. i am saying if you ship one, write what you are trading away.

## write it down like a normal person

you do not need a 10 page architecture doc.

you need a short note that future you can read in 60 seconds.

something like:
- what we decided
- why (one or two lines)
- what we rejected (optional but useful)
- what could make us change this later
- date
- link to the PR / ticket / slack thread

that is it. call it ADR-lite, call it a decision note, call it a sticky. the name does not matter. the write down does.

if a five year old could not tell what you chose, the note is too clever.

## one decision, one note

do not dump ten choices into one mega doc.

one decision per note keeps it searchable. when auth changes, you open the auth note. you do not dig through a novel titled "platform thoughts 2024".

small notes also make it okay to be wrong. you can add a new note that says "we reversed this" instead of editing history until it looks clean.

## date it and link it

a decision without a date is hard to trust.

was this from last week or from before the rewrite? dates answer that.

and link the proof. PR, ticket, design, or the slack message where people agreed. otherwise the note is just another opinion with better formatting.

## make "decided" a real status

half the pain is soft language.

"we were leaning toward X" is not decided.
"X for now" is not decided.
"X, and here is why, and here is when we revisit" is decided.

when something is decided, say it out loud in the note. status: decided. boring words. clear meaning.

## keep it close to the work

if the note lives in a wiki nobody opens, it will rot.

put it where the work already is:
- next to the PR
- in the repo under `/decisions`
- linked from the ticket
- pasted once in the channel, then linked forever

the best process is the one people will actually touch on a busy tuesday.

## what i am trying to do

i am not trying to slow teams down with ceremony.

i am trying to stop paying the tax of forgotten decisions.

the goal is simple:
1. choose
2. write the choice in plain words
3. date it
4. link it
5. move on

later, when someone asks "why is it like this?", you have an answer that still exists.

technical decisions that stick are usually not the smartest ones in the room.

they are the ones somebody bothered to write down.

---

Originally published on [X](https://x.com/aayushchugh/status/2099063541659816018).

### [Client-Server Architecture (How the Web Actually Works)](https://ayushchugh.com/blog/client-server-architecture-how-the-web-actually-works)

Published: 2026-04-12

A practical walkthrough of client-server architecture: how browsers talk to servers over HTTP, what request/response really means, and why the web is built this way.

In the previous article, we defined a system as something that:

> **A set of components working together, where each component has a clear responsibility and they interact through well-defined flows**

And we broke it down into:

Now let's take that idea and apply it to the most common system you use every day:

> **Opening a website**

---

## 1. Let's Start With a Simple Question

When you open a website like https://google.com where is the website actually running?

Not on your laptop.

The data, logic, and processing live somewhere else—on another machine.

So what's happening?

> Your system is split into two parts that need to talk to each other.

---

## 2. The Two Core Components of This System

To make this system work, we divide responsibilities into two components:

1. Client
2. Server

![high-level overview of client-server architecture](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/high-level-overview.jpg)

### Client (The Requester)

This is the part of the system:

- Running on your device
- Directly interacting with the user

Examples:

- Browser
- Mobile app

Responsibilities:

- Capture user actions (click, type, scroll)
- Send requests
- Render responses

---

### Server (The Processor)

This is the part:

- Running on a remote machine
- Responsible for handling requests

Responsibilities:

- Execute business logic
- Fetch/store data
- Return responses

---

### Core Idea

> The client **does not know how things work internally**
>
> It only knows **how to ask**

---

## 3. What Actually Happens Step-by-Step

Let's walk through a real flow.

![What actually happens step-by-step between client and server](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/devtools-inspect.jpg)

**Step 1: You enter a URL**

https://avenueticket.com

The browser decides:

> "I need to ask a server for this"

---

**Step 2: Client sends a request**

This is not vague—it's a structured message.

Example (simplified):

> GET /home

This means give me the home page

---

**Step 3: Server receives the request**

Now the server:

- Parses the request
- Runs logic
- Fetches data (maybe from a database)

---

**Step 4: Server sends a response**

Example:

```json
{
  "title": "Home",
  "user": "Ayush"
}
```

---

**Step 5: Client renders it**

- Converts response into UI
- Displays it to the user

---

### Key Understanding

> The browser is not "loading a website"
>
> It is **requesting data and rendering it**

---

## 4. The Real Backbone: Request → Response

At its core, everything reduces to:

> **Client sends a request → Server sends a response**

### Request Contains:

- What you want (URL/endpoint)
- Method (GET, POST, etc.)
- Optional data

### Response Contains:

- Data
- Status (success/error)

💡 Insight

> This is just **input → processing → output**,
> but happening across machines

---

## 5. How Do They Understand Each Other?

Client and server are independent systems.

So how do they communicate correctly?

### Through a Protocol

On the web, this is:

- HTTP

### What HTTP Defines:

- Format of requests
- Format of responses
- Rules of communication

💡 Insight

> Without a protocol, client and server cannot form a system

---

## 6. Stateless Nature (Very Important)

Here's something subtle but critical:

> The server does **not remember you by default**

### Meaning:

Each request is treated independently:

- Request 1 → processed
- Request 2 → processed separately

**Why?**

Because:

- Simpler design
- Easier scaling
- Better reliability

**Then how do we stay "logged in"?**

Using:

- Cookies
- Tokens

💡 Insight

> State is not built-in—it is **layered on top**

---

## 7. Why This Architecture Works So Well

Client-server became the standard because it solves real problems.

**1. Clear Responsibility Split**

- UI vs Logic

**2. Centralised Data**

- Single source of truth

**3. Scalability**

- Add more servers

**4. Flexibility**

- Same server → web + mobile

💡 Insight

> It's not just about communication—it's about **control and structure**

---

## 8. How This Evolves (But Doesn't Change)

Even modern systems still follow this pattern:

**Traditional Web**

- Server sends full HTML

**Modern Web (SPA)**

- Server sends data
- Client renders UI

**Microservices**

- Many servers instead of one

💡 Insight

> The architecture stays the same
>
> Only the **distribution of responsibilities changes**

---

## 9. Final Mental Model

A system is:

- Components → Client, Server
- Interaction → Request, Response
- Purpose → Deliver functionality

So:

> **Client–Server Architecture =**
> **A system where components communicate over a network to complete a flow**

---

## Final Thought

If you understand this properly, you'll stop thinking in terms of:

- Frontend
- Backend
- APIs

And start thinking:

> **Who is asking? What are they asking? Who is responsible for answering?**

---

## What Comes Next

Now one important question remains:

> When the client sends a request…
> **how does it know where the server is?**

That leads to: DNS & How the Internet Finds Servers

---

## Bonus

If you want to check the request and response cycle of any website, you can right-click on that website and click on Inspect

![Browser context menu with Inspect](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/devtools-network.png)

Click on the network and filter using Fetch/XHR

![DevTools Network tab filtered to Fetch/XHR requests](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/architecture-evolution.jpg)

These are all the requests that your browser is sending to backend server

Now, when you click on any request, you'll see details like **Request URL**, **Request Method**, **Status Code**, etc. I'll cover what all this means in my next article, so stay tuned for that.

![DevTools request headers for an API call](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/request-response-flow.jpg)

Now this is a request, and if you want to see what response the server sent, you can click on the **Response** tab

![DevTools Response tab showing JSON payload](https://cdn.ayushchugh.com/blogs/client-server-architecture-how-the-web-actually-works/server-response-example.jpg)

### [What is a System? (And How Engineers Think About Systems)](https://ayushchugh.com/blog/what-is-a-system-and-how-enginners-think-about-systems)

Published: 2026-04-05

A deep dive into the concept of systems and how engineers think about them.

In the previous article, I talked about why this series is not going to focus on tools, frameworks, or languages.

Because tools change.

But the way you think about systems, that’s what actually makes you an engineer.

So before we talk about backend, frontend, APIs, or databases, we need to answer a very basic question:

> What exactly is a system?

--- 

**A System is Just Input → Processing → Output**

At its core, every system in the world follows a very simple pattern:

![Input → Processing → Output](https://cdn.ayushchugh.com/blogs/what-is-system-and-how-enginners-think-about-systems/input-processing-output.png)

That’s it.

No complexity. No buzzwords.

Let’s make this real.

---

Example 1: Food Delivery App

1. Input: User selects food and places an order
2. Processing:
- Validate user
- Check restaurant availability
- Create order
- Assign delivery partner
3. Output: Order confirmed + tracking details

Example 2: Payment System

1. Input: User clicks “Pay Now”
2. Processing:
- Validate card
- Check balance
- Process transaction
3. Output: Success / Failure response

--- 

Example 3: Chat Application

1. Input: User sends a message
2. Processing:
   - Store message
   - Deliver to the receiver
3. Output: Message appears on the other user’s screen

---

If you look closely, all of these are the same thing.

Just:

![Data coming in, something happens, data goes out](https://cdn.ayushchugh.com/blogs/what-is-system-and-how-enginners-think-about-systems/data-coming-in-something-happens-data-goes-out.png)

---

## Why This Matters

Most beginners jump directly into:

> “I’ll use React”  
> “I’ll build a Node API”  
> “I’ll use MongoDB”

But engineers don’t start there.

They start with fundamental questions:

- **What is the input?**
- **What needs to happen to that input?**
- **What should be the output?**

**Tools come later.**

**Thinking comes first.**

---

## Breaking a System into Parts

Now let’s take this one step further.

In real-world software, this “processing” doesn’t happen in one place.

It’s divided into multiple parts.

A typical system looks like this:

![Diagram showing user, frontend, backend, and database](https://cdn.ayushchugh.com/blogs/what-is-system-and-how-enginners-think-about-systems/user-frontend-backend-database.png)

Let’s understand each part.

---

**1. Frontend (Client)**

This is what the user interacts with.

- Mobile app
- Web app
- Dashboard

Its job is simple:

- Take input from the user
- Show output to the user

It does not handle heavy processing or business logic.

---

**2. Backend (Server)**

This is where the actual processing happens.

- Validates data
- Applies business logic
- Makes decisions

Examples:

- Can the user place this order?
- Is this payment valid?

---

**3. Database**

This is where data is stored.

- Users
- Orders
- Transactions
- Messages

Think of it as the system’s memory.

---

**4. External Services**

Sometimes your system depends on other systems.

For example:

- Payment gateways
- Email services
- SMS providers

You don’t build everything yourself—you integrate.

--

## Understanding Data Flow (This is the Most Important Part)

Now let’s connect everything.

Let’s say a user clicks **“Book Ticket”**.

Here’s what actually happens:

1. User clicks button on frontend
1. Frontend sends request to backend
1. Backend:
   - Validates request
   - Saves data in database
1.  Backend sends response
1.  Frontend shows confirmation

![Frontend user sees result](https://cdn.ayushchugh.com/blogs/what-is-system-and-how-enginners-think-about-systems/frontend-user-sees-result.png)

This flow is everything.

If you understand this, you can build any system.

--- 

## How Engineers Think (This is the Shift)

Beginners think like this:

> “I need to build a React app with a Node backend.”

Engineers think like this:

- What is the input?
- Where does it go?
- Who processes it?
- Where is it stored?
- What is returned?

This shift is what separates:

- Someone who uses tools

vs

- Someone who builds systems

---

## Don’t Think in Tools, Think in Flow

Instead of saying:

> “I’ll use React + Express + MongoDB”

Train yourself to say:

- “User sends data”
- “Server processes it”
- “Database stores it”
- “Response is returned”

Once this is clear, you can implement it using any technology.

---

## A Simple Mental Model You Should Remember

Whenever you see any product, break it like this:

1. What is the input?
1. What processing is happening?
1. What is the output?
1. Where is data stored?
1. How does data flow?

If you can answer these 5 questions, you understand the system.

---

## Wrapping Up

A system is not complicated.

We just make it complicated by jumping into tools too early.

At its core, every system is:

> Input → Processing → Output

And every real-world application is just a combination of:

- Frontend
- Backend
- Database
- External services

connected through **data flow**.

---

## What’s Next

Now that you understand what a system is, the next question becomes:

> How do these parts actually communicate with each other?

In the next article, we’ll break this down and understand:

## Client-Server Architecture (How the Web Actually Works)

That’s where things start getting real.

### [You Don’t Need More Tutorials. You Need Engineering Thinking.](https://ayushchugh.com/blog/you-don't-need-more-tutorials-you-need-enginnering-skills)

Published: 2026-03-22

Tutorials teach syntax and controlled demos, not engineering. This piece defines engineering as building systems that work under real constraints—scale, failure, messy users—and introduces a first-principles series on how systems actually behave, from the network to observability, so you reason instead of follow steps.

Almost 3 million people graduate as engineers each year, but do you actually know what engineering is? There are high chances that you might also be pursuing an engineering degree, but you don't know what engineering actually is.

And honestly, that's not even your fault.

We've built a system where "learning engineering" means:

1. Watching tutorials
1. Memorizing syntax
1. Copying projects
1. and hoping things work

You learn React. You learn Node. You learn some database.

But then reality hits.

You sit down to build some real projects, and suddenly you realise you’re not able to merge all these learnings into something real.

- Things don't scale
- Your code breaks in weird ways
- Performance drops
- Everything feels...fragile

And that's when you realise:

**You didn't learn engineering. You learned tools.**

## So, what is engineering?

To understand what engineering is, you'll have to keep these points in mind

- Engineering is not coding
- Engineering is not frameworks
- Engineering is not even technology

> Engineering is the art of building systems that work under constraints.

And these constraints can be anything, depending on the business needs, like

- Limited resources
- Time
- Unpredictable users
- Scale and Growth
- Failures and edge cases

Anyone can make something that works, but only an engineer can make something that:

- Continues to work
- Works efficiently
- Works when things go wrong
- And works as it grows

## The problem is how we learn

Most learning today is example-driven, not principle-driven.

You follow the steps:

> "Build a TODO app",
"Create an API",
"Deploy your project"

And everything works because the problem is controlled.

But real systems are not controlled.

They are messy.

They involve:

- Multiple users
- concurrent users
- failures
- network delays
- inconsistent states

Tutorials don't prepare you for this. they teach you what to do, not how to think.

## The gap no one talks about

There's a massive gap between:

> "I can build a project"
and
"I can design a system"

This gap is where most developers get stuck.

Because designing systems requires:

- understanding trade-offs
- thinking in flows, not files
- reasoning about failure
- predicting behaviour at scale

And none of this comes from memorising syntax.

## This is what this series is about

This series is not going to teach you:

- A specific language
- A specific framework
- or a step-by-step project

Instead, we're going to do something much more important.

We're going to understand **how systems actually work**.

**From first principles.**

We'll break

- What really happens when you click a button
- How a request travels through the internet
- How does a server handle multiple users
- How databases store and retrieve data
- How frontend and backend communicate
- What "scaling" really means
- Why systems fail and how to design for them

And once we understand this bigger picture, we’ll dive deeper into things that actually matter in real-world systems but are almost never taught in tutorials.

This includes things like:

- data validation and sanitisation
- logging and observability
- caching and performance optimization
- handling security issues
- managing failures and retries
- designing for scale

These things are not frontend or backend dependent. They are required at every single layer of software

No magic.

No black boxes.

Just clear thinking.

## How you should approach this series

Don't read this like a tutorial.

Read it like you're learning how to think.

Pause.
Question things.
Try to visualise what's happening behind the scenes.

Because the goal here is not:

> "Now I can build this project."

This goal is

> "Now I understand how this works in any system"

## Where this leads

Once you start thinking like this:

- Frameworks become tools, not dependencies
- New technologies become easier to learn
- System design starts to make sense
- And debugging becomes logical, not random

You stop guessing.

You start reasoning.

## Final thought

You don't need more tutorials.

You don't need another tech stack.

You don't need another _"build this in 10 minutes"_ video.

What you need is a shift in thinking.

> From coding to engineering, From following steps to understanding systems

And that's exactly what this series is about.

Let's begin.

### [How I Built In-App Notifications in Avenue](https://ayushchugh.com/blog/how-i-built-in-app-notifications-in-avenue)

Published: 2026-03-15

A deep dive into building a scalable notification system for Avenue's event ticketing platform: one central service, one source of truth, real-time delivery via WebSocket, and push notifications—all working together to notify organizers and team members at the right time.

Avenue is an event ticketing platform. Organizers use it to create events and sell tickets.
They manage everything from a dashboard: sales, refunds, disputes, payouts.
This post is about how I built the notification system for that dashboard so the right people get notified at the right time, whether they're in the app or not.
We'll stay at the system design level so you can follow along even if you've never used our product.

## Overall system design

![](https://cdn.ayushchugh.com/blogs/in-app-notifications-avenue/overall-architecture.png)

## The problem we were solving

When something important happens (a new sale, a refund request, a dispute, or something the organizer needs to do like complete Stripe onboarding), we need to tell the right people.
That could be the organizer who owns the account or team members who have access to sales or payments.
We wanted one flow that could do three things: show the notification in the dashboard with a real-time feel, make it still there after a refresh, and send a push notification when they're not on the dashboard.
We didn't want three separate systems. We wanted one "send" that handles all of it.

The core idea: the place we save notifications (we'll call it the store) is the source of truth.
Real-time delivery and push are just two ways we deliver what's already in the store.

```
        Event Happens
 (sale / refund / dispute / action required)
                    │
                    │
                    ▼
        Notification Service
            (Single Entry Point)
                    │
                    │
                    ▼
        Notification Store
        (Source of Truth)
                    │
        ┌───────────┴───────────┐
        │                       │
        ▼                       ▼
   Real-time Delivery       Push Delivery
   (WebSocket / SSE)        (Push Provider)
        │                       │
        ▼                       ▼
  Organizer Dashboard       Mobile / Browser Push
  Instant in-app alert      When user is offline
```

## One store, one pipeline

We keep every notification in a single store. By "store" we mean the place we persist data so it survives restarts and refreshes (in our case a database table). Each notification has who it's for, optional context like which organizer and event it belongs to, the content (title, body, image, extra data), whether it's been read, and when it was created. We can add new kinds of notifications over time without changing how the store is structured. We also index it so we can quickly list notifications by person, filter by organizer, count unread, and sort by time.

The important part: nothing else in the system writes to this store. Every notification is created through one central service. That service is the only thing that can write to the store. It can optionally skip the store, real-time delivery, or push (for future cases like ephemeral or real-time-only notifications). By default it does all three: save to the store first, then deliver in real time, then trigger push. Every part of the product that can cause a notification (a ticket sale, a refund request, a dispute webhook, an account requirement) only talks to this service. It sends a payload like "notify these people about this." It doesn't know about the store, sockets, or push. That keeps the design simple.

Inside the central service we always run the same pipeline in order. Step one: write to the store so we have a source of truth. Step two: send the notification over the real-time channel so anyone with the dashboard open sees it right away. Step three: call the push delivery service so anyone who isn't in the dashboard can get a push. We do those steps in that order every time. If the store write fails we still try real-time and push so the person might still see something. In production we don't skip the store.

```
                Notification Store
                (Database Table)

┌──────────────────────────────────────────────┐
│ notification_id                              │
│ user_id            → Who should receive it   │
│ organizer_id       → Optional context        │
│ event_id           → Optional context        │
│ type               → sale / refund / dispute │
│ title              → Notification title      │
│ body               → Notification body       │
│ image              → Optional image          │
│ metadata           → Extra structured data   │
│ read               → true / false            │
│ created_at         → Timestamp               │
└──────────────────────────────────────────────┘

Indexes

user_id + created_at     → fast inbox queries
organizer_id             → organizer filtering
user_id + read           → unread counts
created_at               → chronological sorting
```

## Who gets the notification

We have to notify the right people. For organizer notifications that's the account owner and any team members who have the right permissions (for example sales, payments, or disputes). We also need to respect their preferences: some want push, some don't; some want email, some don't. We have a dedicated piece that answers "who should get this?" and "what are their preferences?" It returns the list of people and how they want to be notified. When we send a notification we save it and deliver it in real time to everyone on that list. We only send a push to people who have push enabled. That way the logic for "create this notification" stays in one place, and the logic for "who gets it" and "which channel" stays in one place too.

```
                   Notification Service
                           │
                           │
                           ▼
                   Recipient Resolver
      ("Who should receive this notification?")
                           │
                           ▼
               Organizer Access & Permissions
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼

   Account Owner       Team Member A      Team Member B
   permissions: all    permissions:       permissions:
                       sales, refunds     disputes

                           │
                           ▼
                 Preference Evaluation
          ("Which channels are enabled?")
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼

        User A            User B            User C
     push: enabled     push: disabled     push: enabled
     email: off        email: on          email: off

                           │
                           ▼
                    Notification Service
                           │
        ┌──────────────────┴──────────────────┐
        │                                     │
        ▼                                     ▼

   Real-time Delivery                    Push Delivery
 (all authorized users)            (push-enabled users only)
```

## How the dashboard gets its data

The dashboard needs to show a list of notifications, an unread count, and the ability to mark things as read. It also needs to register the device for push (so we know where to send push notifications) and unregister on logout. We expose a small API for all of that. Everything is behind authentication.

The list and count can be scoped. If the logged-in person manages multiple organizers, they can see all their notifications or just one organizer's. We can also show only notifications that aren't tied to a specific organizer (user-level stuff). One store and one API power "all," "this organizer," and "user only" without building separate inboxes.

We don't store links in the store. When we send the list to the dashboard or build the push payload we figure out the right link from the notification type and context (which event, which sale, etc.). The API includes a link for the dashboard and we include a deep link for mobile push. That way we can change our routes or URL structure without migrating data. The dashboard and push always get the right links.

```
             Notification Store

        type: "ticket_sale"
        event_id: evt_42
        sale_id: sale_891
        metadata: {...}

                    │
                    │
                    ▼

             Notification API
         (builds links dynamically)

                    │
        ┌───────────┴────────────┐
        │                        │
        ▼                        ▼

 Dashboard Link              Push Deep Link
 /events/evt_42/sales/891    avenue://event/42/sale/891
```

## Real-time delivery

When someone has the dashboard open we want them to see new notifications right away without refreshing. We run a real-time channel on the same server as the API. When the dashboard connects it sends both its organizer context and the user's identity. The server puts that connection into two logical rooms: one for organizer-wide events (like live cart updates) and one for that user's notifications. The central notification service always sends to the user room. So every device or tab that belongs to that person gets the new notification. One connection covers both kinds of events.

On the dashboard side, when a new notification arrives we don't try to merge it into a local list. We treat the API as the source of truth. We trigger a refresh of the list and unread count and show a toast. The next request to the API brings back the correct order and state. That keeps the UI consistent and avoids having two sources of truth (what came over the wire vs what the API says).

```
                     Organizer Dashboard
                           │
                           │  WebSocket Connection
                           │  (user_id + organizer_id)
                           ▼
                 Real-Time Server (API Server)
                           │
            ┌──────────────┴──────────────┐
            │                             │
            ▼                             ▼

      Organizer Room                 User Room
   (org_123 events)               (user_456 events)

  live cart updates              notifications
  live sales events              personal alerts

                           ▲
                           │
                           │
                 Notification Service
                           │
                           ▼
                     Emit Event
                     to user room

------------------------------------------------------------

              Dashboard Client Behavior

      Real-time event received
                │
                ▼

          Show toast notification

                │
                ▼

        Refresh notifications API

                │
                ▼

      API returns canonical state
  (correct order, unread count, etc.)
```

## What the user sees

The dashboard has a bell icon in the navbar. It shows the unread count from the API. We sometimes add a synthetic item (for example "complete Stripe onboarding") if the organizer hasn't done that yet. Tapping the bell opens a slide-over panel. The panel has tabs: Sales, Disputes, Refund requests, What's New, Actions. We group notifications by type. Sales, Disputes, and Refund requests are always visible. What's New and Actions only show up when there's something in them. Inside each tab we show unread first, then read, with infinite scroll. We only load the list when the panel is open and we know which organizer they're viewing. That way we don't fetch notification data until they need it.

When they tap a notification we decide what to do from the type and the data: open the ticket viewer for a sale or dispute, open the refund dialog for a refund request, send them to Stripe for action-required, or go to the link the API gave us for everything else. We don't keep links in the frontend. We use the link from the API and handlers that know what to do for each type. When they close the panel we mark all as read for the current organizer and refresh the unread count so the badge updates.

[Screen recording of the organizer dashboard: bell, unread badge, and notification panel.](https://cdn.ayushchugh.com/blogs/in-app-notifications-avenue/In-app-notifications-demo.mp4)

## Push notifications

Push uses the same central service and the same pipeline. When we don't skip push the service calls our push delivery layer with the payload. That layer adds the right links and talks to the external push provider. We store device tokens through the API and register them when the user logs in and unregister when they log out. On the dashboard we ask for notification permission once they're logged in and not on a public page, then send the token to our backend. So in-app notifications and push share the same "create notification" path. The only difference is the delivery channel: real-time when they're in the app, push when they're not.

![](https://cdn.ayushchugh.com/blogs/in-app-notifications-avenue/push-notification.jpg)

## Why we did it this way

**Single entry point:** Every feature that needs to notify someone goes through one service. That makes it easy to add logging, feature flags, or new delivery channels later without changing a bunch of call sites.

**Store as source of truth:** Notifications live in the store. They're durable and queryable. Real-time is an optimization. If the real-time channel goes down the next API call still shows the right state. Marking things as read is just updating the store. The next fetch or refresh shows it.

**One store, scoped by organizer:** We don't build a separate inbox per organizer. We use organizer and event as context and filter on them. That keeps the model simple and lets one API support "all," "this organizer," and "user only."

**Computed links:** We never store links in the store. We compute them when we serve the list or build the push payload. When we change routes or deep-link schemes we don't need migrations. The dashboard and push stay in sync.

**Recipients in one place: "Who gets this?"** and "do they want push or email?" live in one component. The rest of the system just sends a payload to the central service. It doesn't deal with permissions or preferences.

**List from API, live via real-time:** The list and count always come from the API. The real-time channel only triggers a refresh and toasts. We don't merge real-time payloads into a local list or worry about order. We refetch and the API is the source of truth. We only fetch the list when the panel is open, which keeps the rest of the app light.

**Tabs and type-based behavior:** Tabs map to notification type so we can add new types without reshaping the UI. What happens when they tap is explicit per type (ticket viewer, refund dialog, Stripe redirect, or the link from the API). Deep links and in-app actions stay consistent.

If you're building notifications for a dashboard or admin tool (organizer-facing or similar), we've found this split works well: one store, one central service, and a clear separation between "what happened" (the store), "deliver now" (real-time), and "deliver later" (push). You can adapt the same ideas to your stack and add more notification types as you go.

### [How I Built a Token-Bucket Rate Limiter for Emails & SMS](https://ayushchugh.com/blog/token-based-rate-limiting-email-sms)

Published: 2025-11-19

Learn how to implement a token-bucket rate limiter with priority queues to throttle email and SMS sending, preventing provider rate limit violations while ensuring critical messages are sent first.

## The Problem

When messages (emails/SMS) start flooding in:

- If I fire off everything as soon as I get it, I will hit provider limits.
- That means rejections, retries, delays — worst case: user waits or critical message fails.
- I already have a queue layer using Amazon SQS, so messages are buffered — but I still needed to throttle consumption to fit the provider limits.

For email: “10 mails per second” → ~1 mail every 100ms.

For SMS: 1600 /minute → ~26.7 sms/sec → ~1 sms every ~37ms (though my implementation focuses on the email side for now).

## The Solution: Token Bucket + Priority

Here’s the logic I implemented in the consumer:

Here’s the logic I implemented in the consumer:

- **Token bucket** with a maximum number of tokens (for email: 10).
- Tokens get refilled at a fixed rate (for email: 10 tokens per second).
  - Each time I want to send an email/SMS, I acquireToken() before sending.
    - If a token is available: consume it → send message.
    - If none are available: wait (queue internally) until tokens refill.
- I introduced priority queues: high-priority vs low-priority messages.
  - High-priority waiters get served first when tokens refill.
  - Low-priority waiters are processed only after all high-priority waiters, given remaining tokens.
- The SQS consumer just pulls messages from SQS, then passes them to this rate-limiting layer. The layer handles when the actual send happens — respecting the token bucket.
  The SQS consumer just pulls messages from SQS, then passes them to this rate-limiting layer. The layer handles when the actual send happens — respecting the token bucket.

Here’s the summary in code (Node + TypeScript style) of the main service:

```typescript
import { logger } from "@avenue/shared";

export class EmailRateLimiterService {
	private tokens: number;
	private readonly maxTokens: number;
	private readonly refillRate: number;
	private lastRefillTime: number;
	private highPriorityWaiters: Array<() => void> = [];
	private lowPriorityWaiters: Array<() => void> = [];
	private processingTimer: NodeJS.Timeout | null = null;

	constructor(maxTokens: number = 10, refillRate: number = 10) {
		this.maxTokens = maxTokens;
		this.tokens = maxTokens;
		this.refillRate = refillRate;
		this.lastRefillTime = Date.now();
	}

	async acquireToken(priority: "high" | "low"): Promise<void> {
		this.refillTokens();

		// Immediate token if available
		if (this.tokens > 0) {
			this.tokens--;
			return;
		}

		// queue based on priority if there are no tokens available
		return new Promise(resolve => {
			if (priority === "high") {
				this.highPriorityWaiters.push(resolve);
			} else {
				this.lowPriorityWaiters.push(resolve);
			}

			// Start processing if not already running
			if (!this.processingTimer) {
				this.processWaiters();
			}
		});
	}

	private processWaiters() {
		this.refillTokens();

		let processed = false;

		// Process high priority waiters first
		while (this.tokens > 0 && this.highPriorityWaiters.length > 0) {
			this.tokens--;
			const resolve = this.highPriorityWaiters.shift()!;
			resolve();
			processed = true;
		}

		// Then process low priority waiters
		while (this.tokens > 0 && this.lowPriorityWaiters.length > 0) {
			this.tokens--;
			const resolve = this.lowPriorityWaiters.shift()!;
			resolve();
			processed = true;
		}

		// Schedule next check for waiters
		if (
			this.highPriorityWaiters.length > 0 ||
			this.lowPriorityWaiters.length > 0
		) {
			const waitTime = Math.max(10, (1 / this.refillRate) * 1000);
			this.processingTimer = setTimeout(() => {
				this.processingTimer = null;
				this.processWaiters();
			}, waitTime);
		} else {
			this.processingTimer = null;
		}

		if (processed) {
			logger.info("Rate limiter processed waiters", {
				module: "email",
				action: "rate-limiter-processed-waiters",
				tokens: this.tokens,
				highWaiters: this.highPriorityWaiters.length,
				lowWaiters: this.lowPriorityWaiters.length,
			});
		}
	}

	private refillTokens() {
		const now = Date.now();
		const elapsed = (now - this.lastRefillTime) / 1000; // seconds
		const tokensToAdd = elapsed * this.refillRate;

		this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
		this.lastRefillTime = now;
	}

	getAvailableTokens() {
		this.refillTokens();
		return Math.max(0, this.tokens);
	}

	getQueueStatus() {
		return {
			availableTokens: this.getAvailableTokens(),
			highPriorityWaiters: this.highPriorityWaiters.length,
			lowPriorityWaiters: this.lowPriorityWaiters.length,
		};
	}
}

export const emailRateLimiter = new EmailRateLimiterService(10, 10);
```

## How It All Fits Together

1. The SQS consumer picks up a message (email or SMS) from the queue.
2. Before actually sending, it calls the rate-limiter's `acquireToken()`, with appropriate priority.
3. If a token is immediately available → send now.
4. If not, the message handler waits (i.e., the `acquireToken()` promise resolves later) until tokens refill and the queued waiter is dequeued.
5. When tokens refill (via `refillTokens()`), `processWaiters()` is triggered and resolves waiters in priority order.
6. This ensures we never send more than our allowed rate, while still using the queue for burst smoothing.

![](https://cdn.ayushchugh.com/blogs/rate-limiting-email-sms.png)

## Why this works?

- **Providers’ limits respected**: You’ll never go above e.g. 10 mails/sec for SES.
- **Back-pressure built in**: Instead of flooding senders, you naturally throttle.
- **Priority handling**: Urgent messages get out quickly, non-urgent ones wait politely.
- **Minimal complexity**: Instead of a full job-queue system with custom rate logic, we reuse SQS + a lightweight in-process limiter.
- **Scalable for SMS too**: Just tune the bucket size and refill rate for your SMS endpoint (e.g., ~26 tokens/sec for 1600 /min).

### [Accepting payments with Tap to Pay using Stripe](https://ayushchugh.com/blog/tap-to-pay-stripe)

Published: 2025-08-24

A step-by-step guide to implement Tap to Pay with Stripe.

## Introduction

Before we start, let me give you the context of why we had to implement this feature. So I am
working on a ticketing platform for events like concerts, fests, etc. We had to implement a way in
which users can buy tickets on the spot.

To improve or ease the process of buying tickets, we decided to implement a feature in which users can just tap their card on the phone of the organizer and pay for the ticket.

## Implementation

We were using stripe to process payments and luckily stripe has the support for reader or point of sale devices built in. So we just had to integrate the stripe sdk in our app and we were good to go.
Before we start, here are the prerequisites:

> ⚠️ This whole implementation was done on iOS. We are using Expo to build the app.

## Prerequisites

1. A Stripe account 😜
1. [Tap to pay entitlement](https://developer.apple.com/documentation/proximityreader/setting-up-the-entitlement-for-tap-to-pay-on-iphone?language=objc) from Apple
1. iPhone XS or later

## Entitlement Configuration

After you add the development entitlement file to your app build target, add the following:

- **Key**: `com.apple.developer.proximity-reader.payment.acceptance`
- **Value type**: `boolean`
- **Value**: `true` or `1`

## Permissions

For iOS, the necessary permissions are automatically handled by the Stripe Terminal SDK. However, you'll need to configure your `app.config.js` (or `app.json` for older Expo versions) with the Stripe Terminal plugin settings.

```json
{
	"expo": {
		"plugins": [
			[
				"@stripe/stripe-terminal-react-native",
				{
					"bluetoothBackgroundMode": true,
					"locationWhenInUsePermission": "Location access is required to accept payments.",
					"bluetoothPeripheralPermission": "Bluetooth access is required to connect to supported bluetooth card readers.",
					"bluetoothAlwaysUsagePermission": "This app uses Bluetooth to connect to supported card readers."
				}
			]
		]
	}
}
```

## Setup the connection token endpoint (server side)

To connect to a reader, your backend needs to give the SDK permission to use the reader with your Stripe account, by providing it with the secret from a ConnectionToken. Your backend needs to only create connection tokens for clients that it trusts.

```js
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

// In a new endpoint on your server, create a ConnectionToken and return the
// `secret` to your app. The SDK needs the `secret` to connect to a reader.
let connectionToken = stripe.terminal.connectionTokens.create();
```

Obtain the secret from the `ConnectionToken` on your server and pass it to the client side.

```js
const express = require("express");
const app = express();

app.post("/connection_token", async (req, res) => {
	try {
		const token = await stripe.terminal.connectionTokens.create();
		res.json({ secret: token.secret });
	} catch (error) {
		console.error("Error creating connection token:", error);
		res.status(500).json({ error: "Failed to create connection token" });
	}
});

app.listen(3000, () => {
	console.log("Running on port 3000");
});
```

## Setup the connection token endpoint (client side)

To give the SDK access to this endpoint, create a token provider function that requests a ConnectionToken from your backend.

```js
import { StripeTerminalProvider } from "@stripe/stripe-terminal-react-native";

const fetchTokenProvider = async () => {
	try {
		const response = await fetch(`${process.env.API_URL}/connection_token`, {
			method: "POST",
			headers: {
				"Content-Type": "application/json",
			},
		});

		if (!response.ok) {
			throw new Error(`HTTP error! status: ${response.status}`);
		}

		const { secret } = await response.json();
		return secret;
	} catch (error) {
		console.error("Error fetching connection token:", error);
		throw error;
	}
};
```

This function is called whenever the SDK needs to authenticate with Stripe or the Reader. It’s also called when a new connection token is needed to connect to a reader (for example, when your app disconnects from a reader). If the SDK can’t retrieve a new connection token from your backend, connecting to a reader fails with the error from your server.

## Initialize the SDK

To get started, pass in your token provider implemented above to StripeTerminalProvider as a prop.

```js
import { StripeTerminalProvider } from "@stripe/stripe-terminal-react-native";

function Root() {
	const fetchTokenProvider = async () => {
		try {
			const response = await fetch(`${process.env.API_URL}/connection_token`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
				},
			});

			if (!response.ok) {
				throw new Error(`HTTP error! status: ${response.status}`);
			}

			const { secret } = await response.json();
			return secret;
		} catch (error) {
			console.error("Error fetching connection token:", error);
			throw error;
		}
	};

	return (
		<StripeTerminalProvider
			logLevel="verbose"
			tokenProvider={fetchTokenProvider}
		>
			<App />
		</StripeTerminalProvider>
	);
}
```

As a last step, call the `initialize` method from `useStripeTerminal` hook. You must call the initialize method from a component nested within `StripeTerminalProvider` and not from the component that contains the `StripeTerminalProvider`.

```js
function App() {
	const { initialize } = useStripeTerminal();

	useEffect(() => {
		initialize();
	}, [initialize]);

	return <View />;
}
```

## Connecting to a reader

To connect to a reader, you need to register it first. You can add a reader in [stripe dashboard](https://dashboard.stripe.com/terminal). For detailed guide, you can refer to [this](https://docs.stripe.com/terminal/payments/connect-reader?terminal-sdk-platform=react-native&reader-type=internet).

## Discovering readers

After registering the reader to your account, search for previously registered readers to connect to your point of sale application with `discoverReaders`, setting `discoveryMethod` to `internet`.

```js
export default function DiscoverScreen() {
	const { discoverReaders, discoveredReaders } = useStripeTerminal({
		onUpdateDiscoveredReaders: readers => {
			// After the SDK discovers a reader, your app can connect to it.
			console.log("Discovered readers:", readers);
		},
	});

	useEffect(() => {
		const fetchReaders = async () => {
			try {
				const { error } = await discoverReaders({
					discoveryMethod: "internet",
				});

				if (error) {
					console.error("Error discovering readers:", error);
				}
			} catch (error) {
				console.error("Failed to discover readers:", error);
			}
		};

		fetchReaders();
	}, [discoverReaders]);

	return <View />;
}
```

## Connecting to a reader

To connect your point of sale application to a reader, call `connectReader` with the selected reader.

```js
try {
	const { reader, error } = await connectReader(
		{
			reader,
		},
		"internet"
	);

	if (error) {
		console.error("connectReader error:", error);
		return;
	}

	console.log("Reader connected successfully", reader);
} catch (error) {
	console.error("Failed to connect to reader:", error);
}
```

## Accepting a payment

To accept a payment, you need to call `createPaymentIntent` method on server side.

```js
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

try {
	const intent = await stripe.paymentIntents.create({
		amount: 1000,
		currency: "usd",
		payment_method_types: ["card_present"],
		capture_method: "manual",
	});

	console.log("Payment intent created:", intent.id);
} catch (error) {
	console.error("Error creating payment intent:", error);
	throw error;
}
```

For Terminal payments, the `payment_method_types` parameter must include `card_present`.

Now call the API to fetch this payment intent on client side.

## Collect payment

After getting the payment intent, you can collect the payment by calling `collectPaymentMethod` method.

```js
try {
	const { paymentIntent: collectedIntent, error: collectError } =
		await collectPaymentMethod({
			paymentIntent: paymentIntent,
			skipTipping: true,
			updatePaymentIntent: true,
			enableCustomerCancellation: true,
			requestDynamicCurrencyConversion: false,
			allowRedisplay: "limited", // Required for Terminal payment method collection
		});

	if (collectError) {
		console.error("Error collecting payment method:", collectError);
		throw collectError;
	}

	console.log("Payment method collected successfully");
} catch (error) {
	console.error("Failed to collect payment method:", error);
	throw error;
}
```

## Confirming the payment

After collecting the payment, you need to confirm the payment by calling `confirmPayment` method.

```js
try {
	const { paymentIntent: confirmedIntent, error: confirmError } =
		await confirmPaymentIntent({
			paymentIntent: collectedIntent,
		});

	if (confirmError) {
		console.error("Error confirming payment:", confirmError);
		throw confirmError;
	}

	console.log("Payment confirmed successfully:", confirmedIntent.id);
} catch (error) {
	console.error("Failed to confirm payment:", error);
	throw error;
}
```

This will capture the payment and you can see the payment details in the stripe dashboard.

## Conclusion

This is how you can implement tap to pay with Stripe on iOS. You can find a sample React Native code [here](https://gist.github.com/aayushchugh/c962710ad6a1a422c8b2fa7584c01d38).

## Important Notes

- **Environment Variables**: Make sure to set `STRIPE_SECRET_KEY` and `API_URL` in your environment variables
- **Error Handling**: Always implement proper error handling in production applications
- **Testing**: Test thoroughly with Stripe's test mode before going live
- **Security**: Never expose API keys in your code; always use environment variables

I took a lot of reference from the Stripe docs and the sample code provided by Stripe for writing this blog. Thanks for reading ❤️

### [The Grand Digital Paradox: Why Indian Government Websites Feel Stuck in Time](https://ayushchugh.com/blog/why-govt-websitesh-have-bad-design)

Published: 2025-07-17

A deep dive into the design challenges faced by Indian government websites and the user experience issues that arise from them.

If you’ve ever tried to pay a challan, check a PF balance, or find information on a scheme online, you’ve likely encountered the Great Indian Website Maze. It’s a familiar journey, often paved with cluttered homepages, broken links, confusing navigation, and a nagging feeling that you’ve just stepped back into the internet of the early 2000s.

In a nation that has mastered complex space missions and is a global software powerhouse, why do our own government's digital front doors feel so unwelcoming? The answer isn't a lack of technical skill. It's a complex tangle of procedural legacy, a misunderstanding of the user base, and a design philosophy that often misses the mark.

## The Root of the Problem: Process Over People

Before we dive into the unique user behaviours, it's crucial to understand the systemic issues that create these digital roadblocks.

1.  **The L1 Tender Trap:** Most government projects are awarded to the vendor who quotes the lowest price (the "L1" bidder). While this seems financially prudent, it's disastrous for design. Quality UX/UI design is an expertise, not a commodity. The L1 system often filters out specialised design firms in favour of vendors who can build a technically functional site for cheap, treating design as an afterthought.

2.  **Design by Committee:** A single website can be subject to approvals from multiple departments and officials, many of whom may lack a background in digital design. This often leads to a chaotic, patchwork design where every department’s request is tacked on, resulting in a cluttered and incoherent user experience.

3.  **Launch and Abandon:** There is often a significant budget for _building_ a website, but little to no allocation for its ongoing maintenance, updates, and iterative improvement. The digital world evolves fast, but these sites are often left frozen in time, gathering digital dust in the form of outdated information and broken links.

## Designing for the Real India: Decoding User Behaviour

This is where it gets interesting for designers. The typical Indian user interacts with the web in ways that often defy Silicon Valley's minimalist gospel. To build for India, we must design for India's unique digital culture.

### The "Show Me Everything" Mentality

A common criticism of these sites is information overload. Homepages are often packed with dense text, dozens of links, scrolling marquees, and blinking "New" icons. While this looks like chaos to a designer trained in minimalism, it’s a direct response to a specific user need.

The Indian user often wants to see **all the information on a single page.** This isn't bad taste; it's a learned behaviour driven by several factors:

- **A Quest for Transparency:** Users often distrust hidden menus and feel that if information is not immediately visible, it might be intentionally obscured.
- **Intermittent Connectivity:** Internet access can be unreliable. Users want to load a single page and have everything they need to read, print, or save for offline access. The ability to hit `Ctrl+P` and get a complete record is a powerful feature.
- **Low Digital Trust:** For many, a government website is a formal, high-stakes interaction. They want to be sure they haven't missed a crucial link or a piece of fine print hidden behind a stylish but non-obvious interface element.

**The Design Challenge:** How do you serve this need without creating a visual mess? The answer isn't to just dump information. The solution lies in smart information architecture. Techniques like well-structured long-scroll pages, collapsible accordion sections (`FAQs`, `Eligibility Criteria`), and clear "Print This Section" or "Download as PDF" buttons can provide that feeling of completeness without sacrificing clarity.

### Supporting the Past: Old Browsers and Operating Systems

India's digital revolution is mobile-first, but it's not mobile-exclusive. A vast number of users still access the internet from older desktop computers in government offices, cyber cafes, Common Service Centres (CSCs), or at home. These machines often run outdated operating systems like Windows 7 and use older browsers that don't support the latest CSS and JavaScript features.

For a designer, this means the trendy, animation-heavy website built with the latest frameworks might not just look bad—it might be completely broken for a significant portion of the user base.

**The Design Solution:** The guiding principle here should be _progressive enhancement_.

- **Start with a Baseline:** Build a core experience that works on all browsers. This means clean, semantic HTML is your foundation. The site must be perfectly usable with just HTML and basic CSS.
- **Layer the Enhancements:** Then, add the advanced CSS for rounded corners, the complex JavaScript for dynamic interactions, and other modern features. Newer browsers will render the enhanced experience, while older browsers will gracefully fall back to the functional baseline. This is a far more robust approach than _graceful degradation_, where you build for the best and hope it doesn't break too badly on the rest.

## The Path Forward: A Blueprint for a Better Digital India

Fixing this isn't impossible. It requires a concerted effort from both policymakers and the design community.

1.  **Rethink Procurement:** Move from the L1 system to a **Quality and Cost-Based Selection (QCBS)** model that gives weightage to technical expertise and design portfolios.
2.  **Champion User Research:** Government departments must invest in understanding their users—their digital literacy, their devices, their anxieties, and their goals.
3.  **Embrace Design Systems:** Initiatives like **UX4G (User Experience for Government)** provide a standardised library of components and templates. Wider adoption can bring much-needed consistency and quality control.
4.  **Prioritise Accessibility:** Compliance with the **Web Content Accessibility Guidelines (WCAG)** shouldn't be a checkbox item; it should be a mandate. It is about ensuring democratic access to information for all citizens, including those with disabilities.
5.  **Empower Internal Teams:** Government departments need to hire or train dedicated teams with product and design management skills to own and iterate on their digital properties.

The design of a government website is more than just aesthetics. It is a reflection of the government's commitment to transparency, efficiency, and citizen service. It’s about building trust in the digital age. For the Indian design community, this isn't just a critique; it's a call to action. Let's engage, advocate, and help build a Digital India that is truly for everyone.

### [Hoisting in JavaScript](https://ayushchugh.com/blog/hoisting-in-js)

Published: 2025-07-13

An overview of hoisting behavior in JavaScript and its implications.

What would you expect the output of the following code to be?

```javascript
a = 2;

var a;

console.log(a);
```

Many developers would expect the output to be `undefined`, since the var a statement comes after the `a = 2`, and it would seem
natural to assume that the variable is redefined, and thus assigned the default `undefined`. However, the output is actually `2`.

Consider another piece of code:

```javascript
console.log(a);

var a = 2;
```

You might be tempted to assume that, since the previous snippet exhibited some less-than-top-down looking behavior, perhaps in this snippet, 2 will also be printed. Others may think that since the a variable is used before it is declared, this must result in a `ReferenceError`.

Unfortunately, the output is `undefined`, which can be quite confusing.

## What is actually happening?

The best way to think about things is that all declarations, both variables and functions, are processed first, before any part of your code is executed.

When you see `var a = 2;` you probably think of that as one statement. But JavaScript actually interprets it as two separate statements: `var a;` and `a = 2;`. The first statement declares the variable `a`, and the second statement assigns the value `2` to it.

Our first snippet is equivalent to:

```javascript
var a;
a = 2;
console.log(a);
```

The second snippet is equivalent to:

```javascript
var a;
console.log(a);
a = 2;
```

## Functions and Hoisting

Functions are also hoisted in JavaScript. Consider the following code:

```javascript
console.log(foo());
function foo() {
	return 2;
}
```

In this case, the output will be `2`, even though the function `foo` is called before it is defined in the code. This is because function declarations are hoisted to the top of their containing scope, just like variable declarations.

## Function Expressions and Hoisting

However, function expressions behave differently. Consider this code:

```javascript
console.log(foo());
var foo = function () {
	return 2;
};
```

In this case, the output will be `TypeError: foo is not a function`. This happens because the variable `foo` is hoisted, but its assignment as a function happens after the `console.log(foo())` line. So when the code tries to call `foo`, it finds that `foo` is `undefined`, leading to the error.

## Summary

Hoisting in JavaScript can be a source of confusion, especially for those new to the language. The key points to remember are:

- Variable and function declarations are hoisted to the top of their containing scope.
- Variable assignments are not hoisted, meaning they are executed in the order they appear in the code.
- Function declarations are hoisted, allowing them to be called before they are defined.
- Function expressions are not hoisted in the same way, leading to potential errors if called before their assignment.

Understanding hoisting is crucial for writing predictable and bug-free JavaScript code. Always be mindful of where and how you declare your variables and functions to avoid unexpected behavior.

### [IndexedDB: Database inside browser](https://ayushchugh.com/blog/indexed-db)

Published: 2025-05-19

IndexedDB is a browser-native NoSQL database API for efficiently storing, querying, and managing large volumes of structured data locally—with support for transactions, indexes, and offline-first applications.

IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs.
Unlike the simple key-value model of `localStorage`, it lets you store and query objects via indexes, run transactions, and work with large volumes of data—making it ideal for offline-first web apps, progressive web apps, and anywhere you need robust local persistence.

## Why IndexedDB?

- **Large storage quotas:** Browsers typically allow hundreds of megabytes (or even more) per origin, versus the 5 MB limit of localStorage.
- **Structured objects:** Store complex JavaScript objects directly—no need to stringify.
- **Indexes & queries:** Define secondary indexes for efficient lookups by object properties.
- **Transactions:** Atomic reads and writes across multiple object stores.
- **Asynchronous API:** Prevents UI blocking, especially important for large datasets.

## Core Concepts

1. **Database:** A logical container identified by name and version.
2. **Object Store:** Like a table in SQL, holds records (JavaScript objects).
3. **Key & Key Path:** Each record has a primary key. You can let IndexedDB auto-generate it or specify a key path (a property name).
4. **Index:** Secondary lookup on object properties. Allows queries like “find all users by email.”
5. **Transaction:** Defines scope for reading/writing. Transactions can be read-only or read-write across one or more object stores; they either fully succeed or roll back.
6. **Cursor:** Iterates over records in an object store or index.

## Opening a Database

```js
const request = indexedDB.open('MyAppDB', 1);

request.onupgradeneeded = event => {
  const db = event.target.result;
  // Create an object store named "contacts" with auto-incremented keys
  const store = db.createObjectStore('contacts', { keyPath: 'id', autoIncrement: true });
  // Create an index on the "email" property
  store.createIndex('by_email', 'email', { unique: true });
};

request.onsuccess = event => {
  const db = event.target.result;
  // db is now ready for operations
};

request.onerror = event => {
  console.error('Database error:', event.target.error);
};
```

## CRUD Operations

### Adding or Updating Records

```js
function saveContact(db, contact) {
  const tx = db.transaction('contacts', 'readwrite');
  const store = tx.objectStore('contacts');
  // .put() will insert or update based on the keyPath
  store.put(contact);
  return tx.complete;
}

// Usage:
request.onsuccess = e => {
  const db = e.target.result;
  saveContact(db, { name: 'Alice', email: 'alice@example.com' })
    .then(() => console.log('Saved successfully'))
    .catch(err => console.error(err));
};
```

### Reading Records

```js
function getContactByEmail(db, email) {
  const tx = db.transaction('contacts', 'readonly');
  const index = tx.objectStore('contacts').index('by_email');
  return index.get(email); // returns a request; wrap in Promise if you like
}

// Usage:
getContactByEmail(db, 'alice@example.com')
  .onsuccess = e => console.log('Contact:', e.target.result);
```

### Deleting Records

```js
function deleteContact(db, id) {
  const tx = db.transaction('contacts', 'readwrite');
  tx.objectStore('contacts').delete(id);
  return tx.complete;
}
```

### Example: Viewing and Deleting Contacts

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>IndexedDB Contacts Example</title>
  <style>
    table { border-collapse: collapse; width: 100%; margin-top: 1em; }
    th, td { border: 1px solid #ccc; padding: 0.5em; text-align: left; }
    button { padding: 0.25em 0.5em; }
  </style>
</head>
<body>
  <h1>IndexedDB Contacts</h1>

  <form id="contactForm">
    <label for="name">Name</label>
    <input id="name" type="text" placeholder="Name" required />

    <label for="email">Email</label>
    <input id="email" type="email" placeholder="Email" required />

    <button type="submit">Save</button>
  </form>

  <h2>All Contacts</h2>
  <table id="contactsTable">
    <thead>
      <tr>
        <th>ID</th><th>Name</th><th>Email</th><th>Actions</th>
      </tr>
    </thead>
    <tbody><!-- rows injected here --></tbody>
  </table>

  <script>
    function openDB() {
      return new Promise((resolve, reject) => {
        const request = indexedDB.open("myDB", 2);
        request.onupgradeneeded = () => {
          const db = request.result;
          if (!db.objectStoreNames.contains("contacts")) {
            const store = db.createObjectStore("contacts", {
              keyPath: "id", autoIncrement: true
            });
            store.createIndex("by_email", "email", { unique: true });
          }
        };
        request.onsuccess = () => resolve(request.result);
        request.onerror   = () => reject(request.error);
      });
    }

    function saveContact(db, contact) {
      return new Promise((resolve, reject) => {
        const tx    = db.transaction("contacts", "readwrite");
        const store = tx.objectStore("contacts");
        const req   = store.put(contact);
        req.onsuccess = () => resolve(req.result);
        req.onerror   = () => reject(req.error);
      });
    }

    function getAllContacts(db) {
      return new Promise((resolve, reject) => {
        const tx    = db.transaction("contacts", "readonly");
        const store = tx.objectStore("contacts");
        const req   = store.getAll();
        req.onsuccess = () => resolve(req.result);
        req.onerror   = () => reject(req.error);
      });
    }

    function deleteContact(db, id) {
      return new Promise((resolve, reject) => {
        const tx    = db.transaction("contacts", "readwrite");
        const store = tx.objectStore("contacts");
        const req   = store.delete(id);
        req.onsuccess = () => resolve();
        req.onerror   = () => reject(req.error);
      });
    }

    async function renderContacts() {
      const db       = await openDB();
      const contacts = await getAllContacts(db);
      const tbody    = document.querySelector("#contactsTable tbody");
      tbody.innerHTML = "";
      contacts.forEach(contact => {
        const tr = document.createElement("tr");
        tr.innerHTML = `
          <td>${contact.id}</td>
          <td>${contact.name}</td>
          <td>${contact.email}</td>
          <td><button data-id="${contact.id}">Delete</button></td>
        `;
        tbody.appendChild(tr);
      });
      tbody.querySelectorAll("button").forEach(btn => {
        btn.addEventListener("click", async () => {
          const id = Number(btn.dataset.id);
          await deleteContact(await openDB(), id);
          renderContacts();
        });
      });
    }

    document.getElementById("contactForm")
      .addEventListener("submit", async e => {
        e.preventDefault();
        const name  = e.target.name.value.trim();
        const email = e.target.email.value.trim();
        if (!name || !email) return;
        const db = await openDB();
        await saveContact(db, { name, email });
        e.target.reset();
        renderContacts();
      });

    // initial render
    renderContacts();
  </script>
</body>
</html>
```

### Output

![output of above codebase](https://cdn.ayushchugh.com/blogs/indexed-db/example.png)

## Best Practices

- **Versioning & Migrations:** Handle schema upgrades carefully in `onupgradeneeded`.
- **Error Handling:** Always set `onerror` on requests and transactions.
- **Transactions Scope:** Keep transactions short; long-running ones can time out.
- **Feature Detection:** Fallback to `localStorage` or in-memory if IndexedDB isn’t available:

```js
if (!window.indexedDB) {
  console.warn("IndexedDB not supported — falling back.");
  // fallback logic here
}
```

- **Cleanup:** Implement logic to purge stale data or compact large stores.
- **Security:** Never store sensitive data unencrypted.

## Real-World Use Cases
- **Offline-first Apps:** Cache API responses and serve from IndexedDB when offline.
- **Large Media Storage:** Store images, audio, or video blobs.
- **Form Drafts:** Auto-save user inputs in progress.
- **Analytics & Logs:** Buffer log events locally before batching to server.

## Conclusion

IndexedDB unlocks powerful client-side storage capabilities that go far beyond simple key-value stores.
Whether you’re building a PWA that works offline, storing user drafts, or caching media, mastering its concepts—databases, object stores, indexes, and transactions—will help you create resilient, high-performance web applications.

### [Mastering view port: 100vh scroll problem](https://ayushchugh.com/blog/mastering-view-ports)

Published: 2025-05-04

A deep dive into CSS viewport units (vh, vw) and how the new small (sv*), large (lv*), and dynamic (dv*) viewport units solve the annoying 100vh scrolling issue on mobile and dynamic-browser UIs.

You must have used View Port units _(vh, vw)_ in CSS, but do you know how this View Port works and why is there annoying scroll if you use _100vh_?

## Basics of view port

1. `vw = 1%` of the width of the viewport size.
1. `vh = 1%` of the height of the viewport size.

If we use 100vw and 100vh, it will cover the entire document.

![100vh in a laptop](https://cdn.ayushchugh.com/blogs/viewports/viewports-100vh-laptop.png)

This can work fine in desktops, but in mobiles (or desktop sites with navbars), where we have dynamic headers and status bars. Our content will overflow out of the actual viewport, resulting in a scroll state.

![100vh in a mobile](https://cdn.ayushchugh.com/blogs/viewports/viewports-100vh-mobile.png)

To overcome this issue, new view port units were introduced, i.e. small view port and large view port

## Small Viewport

Small view port assumes that dynamic status bars are open, i.e. default state of browser when you visit a website without scrolling.

Units representing the small viewport have the sv prefix. The units are `svw`, `svh`, `svi`, `svb`, `svmin`, and `svmax`

![Small viewport](https://cdn.ayushchugh.com/blogs/viewports/viewports-small.png)

## Large Viewport

Large view port assumes that dynamic status bar are not open i.e. scrolled state.

Units representing the large viewport have the lv prefix. The units are `lvw`, `lvh`, `lvi`, `lvb`, `lvmin`, and `lvmax`

![Large viewport](https://cdn.ayushchugh.com/blogs/viewports/viewports-large.png)

## Dynamic view port

In addition to the large and small viewports, there‘s also a dynamic viewport that has dynamic consideration of the UI

1. When the dynamic toolbars are expanded, the dynamic viewport is equal to the size of the small viewport.
1. When the dynamic toolbars are retracted, the dynamic viewport is equal to the size of the large viewport.

Its accompanied units have the `dv` prefix: `dvw`, `dvh`, `dvi`, `dvb`, `dvmin`, and `dvmax`. Their sizes are clamped between their lv* and sv* counterparts.

![Dynamic viewport](https://cdn.ayushchugh.com/blogs/viewports/viewports-dynamic.png)

## Conclusion

Using `100vh` for full-height layouts can lead to unwanted scrolling when browser UI elements (like address bars or status bars) dynamically show or hide. The new viewport unit families—**small viewport** (`sv*`), **large viewport** (`lv*`), and especially the **dynamic viewport** (`dv*`)—give you precise control over sizing in both static and interactive browser states. For most modern responsive designs, switch from `vh` to `dvh` (dynamic viewport height) to ensure your content always fits exactly without overflow or scroll artifacts, regardless of whether the user has scrolled or the browser UI is visible.

### [Progressive Web Apps: Create apps from websites](https://ayushchugh.com/blog/pwa-create-apps-from-websites)

Published: 2025-01-25

Progressive Web Apps (PWAs) are web applications that are built using web technologies but behave like native apps. They are fast, reliable and engaging.

Ever thought of converting your website into an app? Progressive Web Apps (PWAs) are here to help you with that.

## What are Progressive Web Apps (PWAs)?

Progressive Web Apps (PWAs) are downloadable web applications that provide a native app-like experience on the web. They are built using web technologies like HTML, CSS, and JavaScript but behave like native apps. PWAs are fast, reliable, and engaging, making them an ideal choice for modern web applications.

### PWAs allow you to:

1. Deploy updates instantly without waiting for app store approval
2. Create cross-platform applications with a single codebase
3. Provide native-like features such as home screen installation and push notifications

## Key Characteristics of PWAs

1. **Progressive Enhancement**:

   - PWAs are built with the principle of progressive enhancement, meaning they start as basic web pages and add layers of advanced features as the browser capabilities and device allow. This ensures a functional experience on any browser, including older or less capable ones, while offering enhanced features on modern browsers.

1. **Installability**:
   - Users can “install” a PWA directly onto their device’s home screen or desktop from the browser. This creates a shortcut icon and an app-like experience without going through an app store.
   - Once installed, PWAs can open in a standalone window without typical browser interface elements, making them feel more like native apps.
1. **Offline Capabilities & Reliability**:

   - PWAs use Service Workers—scripts that run in the background independent of the webpage—to enable offline or near-offline experiences.
   - By caching essential assets (HTML, CSS, JavaScript, images) in advance, PWAs can load instantly even on slow networks or with no connectivity, providing a reliable user experience.

1. **Responsive Design**:

   - PWAs are fully responsive, allowing them to work on various devices (phone, tablet, desktop) with different screen sizes and orientations.

1. **Re-Engagement Tools**:

   - Modern web APIs enable features like push notifications and background sync, allowing PWAs to keep users engaged, similar to native apps.

1. **Secure Context (HTTPS)**:
   - Service Workers and many advanced web APIs only work over secure connections (HTTPS). PWAs require HTTPS to ensure data integrity and security.

## How PWAs Work Technically

1. **Web App Manifest**:
   - This is a JSON file that provides metadata about the application (name, icons, theme color, etc.).
   - The manifest allows browsers to understand how the app should appear when installed on the device and what the start screen should look like.
1. **Service Worker**:
   - A Service Worker is a script that runs in the background, separate from the main browser thread.
   - It can intercept network requests, manage caching, and handle push notifications.
   - The Service Worker is what enables offline functionality by caching resources and serving them when network access is slow or absent.
1. **App Shell Architecture**:
   - A common architectural approach used in PWAs where the “shell” (minimal HTML, CSS, JavaScript) is cached on the device.
   - The content (or data) is dynamically loaded and updated from the network as needed. This approach ensures the app feels fast and responsive even if the network is unreliable.

## Benefits of Using PWAs

1. **Improved Performance and Speed**

   - Because of caching assets, PWAs often load faster than traditional websites, leading to better user engagement and retention.

1. **Better User Engagement**

   - Installing a PWA on the home screen makes it just one tap away from the user.
   - Push notifications and offline capabilities keep the app “sticky” and help bring users back.

1. **Cross-Platform Compatibility**

   - A single codebase (using standard web technologies: HTML, CSS, JavaScript) can run on multiple platforms (Android, iOS, Windows, macOS, etc.).

1. **Lower Development and Maintenance Costs**

   - Compared to creating separate native apps for Android and iOS, a PWA can be built once and deployed everywhere the web is supported.

1. **Discoverability**

   - PWAs are discoverable by search engines since they are essentially websites. This is a big advantage over native apps, which are generally only discoverable via app stores.

1. **No Mandatory App Store Dependencies**

   - While PWAs can be published in some app stores (e.g., Google Play), they do not require it. Users can install them directly from the browser.

## When to Consider a PWA

1. You want to reach the widest audience possible with minimal friction (no app store barrier).
1. Your users need offline or low-bandwidth functionality, such as reading content or basic browsing.
1. Budget and development resources are limited (a single codebase for all platforms).
1. Rapid updates are important (PWAs can be updated instantly from the web, whereas native apps often require store approval).
1. SEO matters and you want to be discoverable via search engines.

## Creating a PWA using NextJS

1. Set Up Your Next.js Project

If you haven't already set up a Next.js project, follow [this](/blog/initial-setup) guide to create a new Next.js app

2. Create a Web App Manifest file in the app directory

Create a `manifest.ts` file `app` directory with following content:

```ts
import type { MetadataRoute } from "next";

export default function manifest(): MetadataRoute.Manifest {
	return {
		name: "Ayush Chugh", // Your app name
		short_name: "ayushchugh", // Short name for your app
		description:
			"Passionate full stack developer from India. On the way to master programming", // Description of your app
		start_url: "/", // Start URL when the app is launched
		id: "com.ayushchugh", // Unique identifier for your app
		display: "standalone", // Display mode of your app
		background_color: "#000000", // Background color of your app
		theme_color: "#000000", // Theme color of your app
		// Add icons for your app
		icons: [
			{
				src: "/favicon/web-app-manifest-192x192.png",
				sizes: "192x192",
				type: "image/png",
				purpose: "any",
			},
			{
				src: "/favicon/web-app-manifest-512x512.png",
				sizes: "512x512",
				type: "image/png",
				purpose: "any",
			},
		],
	};
}
```

You can generate icons for your app using [favicon generator](https://realfavicongenerator.net/) and place them in the `public/favicon` directory.

This will create a Web App Manifest file for your PWA and your application becomes installable after adding this file.

To create Service Workers and Enable push notifications, follow official [Next.js documentation](https://nextjs.org/docs/app/building-your-application/configuring/progressive-web-apps).

## Challenges or Limitations

1. **Limited Access to Device Features**:
   - Although modern browsers have APIs for camera, geolocation, push notifications, etc., some advanced native features may be restricted or require special permissions. Features like Bluetooth, NFC, or certain OS-level integrations vary by browser and operating system.
2. **iOS Support Gaps**:
   - Over the years, iOS (Safari) has gradually added more PWA features, but some capabilities—like push notifications (which arrived relatively late on iOS)—may still have constraints compared to Android or desktop browsers.
3. **User Awareness**:
   - Some users might not know how to “install” a PWA because this process differs from traditional downloading in app stores.
4. **Performance on Older Devices/Browsers**:
   - While progressive enhancement helps, the full PWA experience relies on modern browser APIs. Older devices may not benefit from advanced capabilities like offline mode or push notifications.

## Conclusion

Progressive Web Applications represent a significant evolution in how we build and deliver software experiences on the web. They blend the reach and simplicity of the web with many of the convenience and engagement features of native apps. By utilizing modern browser APIs—like Service Workers and Web App Manifests—PWAs provide offline access, push notifications, and app-like interfaces, all in a single, cost-effective package.

As browser and operating system support continues to improve, PWAs offer an increasingly powerful way for businesses and developers to create high-performing, cross-platform solutions that are easy for users to find, install, and use.

### [Load Shedding: Scaling down to scale up](https://ayushchugh.com/blog/load-shedding)

Published: 2024-10-14

Load shedding is a technique to scale down the resources of a system to prevent it from crashing under heavy load.

Ever wondered when you visit a website, why certain components of the websites are working but others are not?
How does a system prevent itself from crashing under heavy load?
How a system can handle a sudden spike in traffic?

The answer is load shedding. Load shedding is a technique to scale down the resources of a system to prevent it from crashing under heavy load.
In this article, we will discuss how load shedding works and how to implement it in a microservices architecture.

Before we dive into load shedding, let's understand the basics of microservices architecture.

You can directly jump to the section on [Load Shedding](#what-is-load-shedding) if you are already familiar with microservices architecture.

## Overview of microservices
Microservices architecture divide a complex system into smaller, manageable services. Each service is responsible for a specific task and can be deployed
independently. This makes system more scalable, flexible and easier to maintain.

Microservices architecture is widely used by companies like Google, Netflix, Amazon, Uber, etc. This architecture allows companies to scale their system as per the demand.

Microservices architecture is a great choice for large, complex applications that have a lot of traffic but it is not suggested for small applications as it adds complexity to the system and
it is expensive to maintain.

### Example of microservices architecture

Let's take an example of a food delivery application like Zomato, Swiggy or DoorDash.

![Basic architecture of microservices](https://cdn.ayushchugh.com/blogs/load-shedding/microservices-basics.png)

The application can be divided into following services:

1. User Profile
1. Payments
1. Products
1. Product Images

When a user visits the application, the request is routed to the API Gateway which then routes the request to the respective service.
User Profile service is responsible for managing user profiles, Payments service is responsible for managing payments, Products service is responsible for managing products and Product Images service is responsible for managing product images.

Each service can be deployed independently and can be scaled as per the demand. This makes the system more scalable and flexible. If the Products service is getting a lot of traffic, it can be scaled independently without affecting other services.

### Advanced architecture of microservices

![Advance architecture of microservices](https://cdn.ayushchugh.com/blogs/load-shedding/microservices-advanced.png)
Now after adding basic microservices, we need more tools to manage the system and make it more scalable.

We have added 3 more services to the system:
1. **Monitoring and Logging:** This service is responsible for monitoring the system and logging the events. It helps in identifying the issues in the system.
1. **Service Registry:** This is a database of all the services in the system. It helps in routing the request to the respective service.
1. **Simple Storage Service:** Also known as AWS S3, this is an object storage bucket where all the product images are stored.

Now that we have understood the basics of microservices architecture, let's dive into load shedding.

## What is Load Shedding?

Load shedding is the practice of sacrificing enough application traffic to keep partial availability in the presence of an overload condition.
Used in conjunction with strategies like load balancing, load shedding helps applications support service level agreements (SLAs) when increased traffic overwhelms available system resources.

To understand this better, let's prioritize the services in the food delivery application.
1. **User Profile:** User Profile service is the most important service as it is responsible for managing user profiles. If the User Profile service is down, the user will not be able to login to the application.
1. **Payments:** Payments are the second most important service as it is backbone of any business.
1. **Products:** Products service is the third most important service as it is responsible for managing products.
1. **Product Images:** Product Images service is the least important service as it is responsible for managing product images.

Now let's say the system is under heavy load and we can't add more instances to the system. In this case, we can shed (shut down) the least important service i.e. Product Images service
to prevent the system from crashing.

We will reallocate the resources of Product Images service to the Payments service so that the system can handle the load.

![](https://cdn.ayushchugh.com/blogs/load-shedding/microservices-load-shedded.png)
This is what load shedding looks like in a microservices architecture.

## Conclusion
Load shedding is a technique to scale down the resources of a system to prevent it from crashing under heavy load. It is a great way to handle sudden spikes in traffic and keep the system up and running.

### [Self hosting Docmost, an open-source alternative to Notion](https://ayushchugh.com/blog/self-hosting-docmost)

Published: 2024-10-06

A step-by-step guide to deploy Docmost on your VPS and setting up a custom domain.

Ever thought of privacy concerns while using 3rd party apps? If so, docmost is the best solution for you.

[Docmost](https://docmost.com) is an open-source alternative to Confluence and Notion, which you can self-host and all the data stays on your servers. It is build using React, Node.js and PostgreSQL.

Self hosting docmost may sound like a daunting task, but it's not that hard. In this guide, I will show you how to deploy docmost on your VPS and setting it up a custom domain.

## Prerequisites

1. A VPS ofcourse.
1. A domain name.
1. Basic knowledge of Linux and Docker.

## Tools that we will be using

1. Docker
1. Nginx (for reverse proxy)
1. Certbot (for SSL)

## Step 1: Setting up the environment

1. SSH into your VPS. I am using a VPS with Ubuntu 20.04.

```bash
ssh user@your-vps-ip
```

2. Install Docker.

Set up Docker's apt repository.

```bash
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
```

Install the Docker packages.

```bash
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Verify that the Docker Engine installation is successful by running the hello-world image.

```bash
sudo docker run hello-world
```

You should see the following output:

```bash
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm64v8)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

3. Install Nginx

```bash
sudo apt install nginx
sudo systemctl start nginx
```

## Step 2: Deploying Docmost

1. Create `docker-compose.yml` file with the following content.

```yaml
services:
  docmost:
    image: docmost/docmost:latest
    depends_on:
      - db
      - redis
    environment:
      APP_URL: "http://localhost:3000"
      APP_SECRET: "REPLACE_WITH_LONG_SECRET"
      DATABASE_URL: "postgresql://docmost:STRONG_DB_PASSWORD@db:5432/docmost?schema=public"
      REDIS_URL: "redis://redis:6379"
     MAIL_DRIVER: 'smtp'
      SMTP_HOST: 'REPLACE_WITH_SMTP_HOST'
      SMTP_PORT: 'REPLACE_WITH_SMTP_PORT'
      SMTP_USERNAME: 'REPLACE_WITH_SMTP_USERNAME'
      SMTP_PASSWORD: 'REPLACE_WITH_SMTP_PASSWORD'
      MAIL_FROM_ADDRESS: 'REPLACE_WITH_SMTP_EMAIL'
      MAIL_FROM_NAME: 'REPLACE_WITH_SMTP_NAME'
    ports:
      - "3000:3000"
    restart: unless-stopped
    volumes:
      - docmost:/app/data/storage

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: docmost
      POSTGRES_USER: docmost
      POSTGRES_PASSWORD: STRONG_DB_PASSWORD
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/postgresql/data

  redis:
    image: redis:7.2-alpine
    restart: unless-stopped
    volumes:
      - redis_data:/data

volumes:
  docmost:
  db_data:
  redis_data:
```

If you want to invite others to use this workspace, you will need to add SMTP variables to send invitation through emails. You can use [Zoho](https://mail.zoho.com) to get free custom emails.

> Make sure to update environment variables with your own values and take a look at [supported variables](https://docmost.com/docs/self-hosting/environment-variables/).

2. Run the following command to run the container.

```bash
docker compose up --build -d
```

3. Visit `http://your-vps-ip:3000` to see docmost running.

## Step 3: Setting up custom domain with Nginx

1. Visit your domain registrar and add an A record pointing to your VPS IP.
2. Create a new Nginx configuration file.

```bash
touch /etc/nginx/sites-available/docmost
```

3. Add the following content to the file.

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name <domain_name> www.<domain_name>;

    # Redirect HTTP to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name <domain_name> www.<domain_name>;

    location / {
        proxy_pass http://localhost:<project_port>/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

> Make sure to replace `<domain_name>` with your domain name and `<project_port>` with the port on which docmost is running.

4. Create a symbolic link to the `sites-enabled` directory.

```bash
sudo ln -s /etc/nginx/sites-available/docmost /etc/nginx/sites-enabled/docmost
```

5. Restart Nginx.

```bash
sudo systemctl restart nginx
```

## Step 4: Setting up SSL with Certbot

1. Install Certbot.

```bash
sudo apt install python3-certbot-nginx -y
```

2. Obtain SSL certificate.

```bash
sudo certbot --nginx -d <domain_name> -d www.<domain_name>
```

3. Restart Nginx.

```bash
sudo systemctl restart nginx
```

## Conclusion

You have successfully deployed docmost on your VPS and set up a custom domain with a free SSL certificate. You can now access docmost at `https://your-domain.com`.

Feel free to reach out to me on [X](https://X.com/ayushchugh_) or drop an [Email](mailto:hey@ayushchugh.com) if you have any questions or need help.

## References

1. [Docmost](https://docmost.com)
1. [Docker Documentation](https://docs.docker.com)
1. [Nginx Documentation](https://nginx.org/en/docs/)
1. [Certbot Documentation](https://certbot.eff.org/docs/)
1. [Zoho Mail](https://mail.zoho.com)

### [Unlocking Beckn: The Unsung Hero of eCommerce Revolution](https://ayushchugh.com/blog/unlocking-beckn-the-unsung-hero-of-eCommerce-revolution)

Published: 2024-09-23

Connecting multiple platforms without the need for a central authority.

Ever wondered how you pay using google pay but the reciever recieves a notification on phone pay?
How ONDC is able to show products from Amazon, Flipkart and other platforms?

The answer is [Beckn](https://becknprotocol.io). Beckn is a protocol that allows different platforms to communicate with each other without the need for a central authority.
It is a decentralized protocol that allows for the exchange of information between different platforms.
Beckn is the unsung hero of the eCommerce revolution, connecting multiple platforms and enabling seamless transactions.

## The Problem

The eCommerce industry is growing rapidly, with more and more people shopping online.
However, the current system is fragmented, with different platforms using different technologies and protocols.
This makes it difficult for platforms to communicate with each other and share information.
We need a centralised protocol to connect these platforms with each other.

## The Solution

This is where Beckn comes in. Beckn (short for beckn protocol) is an open protocol for decentralized commerce.
It allows different platforms to communicate with each other without the need for a central authority.
It consists of several specifications for building open e-commerce networks across which sellers and their offers are universally discoverable from any Beckn-enabled app or platform.

## What is Beckn?

The Beckn Protocol de-couples the demand-side digital infrastructure (apps and other channels) from the supply-side service provisioning infrastructure.
It does this by making integrated location-aware services available not just on a single platform but potentially on any online consumer interface (online maps, messaging, wallets, voice assistant apps and devices) that has mainstream adoption in a city.

Beckn is a protocol, not a platform. It has a decentralized architecture that obviates the need for creating a centralized platform in order to integrate services from multiple providers simultaneously.
The protocol ensures privacy and security by design by enabling secure, privacy-protected interactions.

The Interoperability protocol allows two entities in an integrated ecosystem to communicate with each other through structured flows and role definitions.
The Server-To-Server Communication protocol allows any consumer-facing online platform to discover and transact with location-aware remote services with minimal implementation overhead.
A single message packet generated at run-time, hence, could contain services from multiple providers.

## How Beckn Works

Beckn is a multi-layerd, with layer structure and organisation resembling that of HTTP.
The architecture of the Beckn ecosystem has five layers (from top to bottom):

1. Application layer
1. Network and Transaction layer
1. Infrastructure and Security layer
1. Certification layer
1. Specification and Support layer

All components of the Beckn ecosystem are, as of July 2021, live and operational on production platforms except for the Certification component which has one of its specifications still under development.
There are four Beckn domain-based communities that work to implement Beckn in their respective industries and, as a result of their work, four Beckn-enabled networks.

### Multi-Layered Structure of the Protocol

Beckn architecture prescribes multiple layers stacked on top of one another, each with clearly defined roles and functions, as evident from this diagram:

![Architecture of beckn](https://cdn.ayushchugh.com/blogs/beckn-architecture.png)

Every entity that wants to be part of the network must be in one or more of the layers listed in the left column.

The bottom two layers — the Specification and Support layer and the Certification layer — provide all the resources and support required to implement the network, to test for compliance and to become certified to join the network.

The top two layers are the actual run-time of the specification in the form of the Application layer which constitutes the consumer and the provider interfaces, and the Network and Transaction layer which has the server-side applications and the routing infrastructure.

In the very middle is the Infrastructure and Security layer.
This layer holds the underlying infrastructure that transforms the Beckn-compliant platforms into actual live transacting entities on an open network.
The layer comprises of open registries which list the various platforms and makes them this discoverable by other platforms — a role similar to that of the domain name system (DNS) in HTTP.

Each of these layers has undergone unique independent evolution in terms of complexity and scale.
Together, they all work in concert to create the ecosystem of Beckn.

## Apps powered by Beckn

1. [ONDC](https://ondc.org)

## Conclusion

Beckn is the unsung hero of the eCommerce revolution. It is a protocol that allows different platforms to communicate with each other without the need for a central authority.
It is a decentralized protocol that connects multiple platforms and enables seamless transactions.

Beckn is a new protocol so it has less community support and adoption but it is growing rapidly and is expected to become the standard for eCommerce transactions in the future.

## References

1. [Beckn Protocol](https://becknprotocol.io)
2. [Beckn Docs](https://developers.becknprotocol.io)
3. [Beckn Github](https://github.com/beckn)

### [How I started my journey in web development as 13 year old](https://ayushchugh.com/blog/how-i-started-my-journey-in-web-development-as-13-year-old)

Published: 2024-09-13

A guide to setting up your Next.js project efficiently.

Hey there! I am Ayush Chugh, a full-stack web developer from India. This all goes back to 2020 when the whole world was under lockdown. I was enjoying it because I didn’t need to go to school. Who doesn’t love vacations, right? Then I thought, “Ayush, why waste time? Let’s do something productive.” That’s when I decided to start learning how to code and make the most of my time.

The first programming language I picked was C, but after writing some code, I realized it wasn’t for me. So, I left it and went back to playing games for a couple of days. A few days later, my brother was making a website for his college project and he asked me how it looked. I recommended some changes, and as he opened VSCode, I was attracted to its theme. I thought, “This thing looks cool. Let’s give it a try.”

So, I asked him to teach me, and he shared some YouTube videos with me. I started learning HTML and was highly satisfied and motivated by the actual output of the HTML code. I realized I could create my own websites, so I kept learning. I spent around a week learning HTML and CSS.

After that, I did some research on what to learn next and what was in demand in the market. I wanted to know which languages and technologies had a promising future. I decided to learn JavaScript and Git. Following that, I learned SCSS. In February 2021, I built my first live project, [a2infinite](https://a2infinite.com), using HTML, CSS, and SCSS. I completed this project in May 2021.

Next, I wanted to learn how logic is handled on servers, so I did some research and discovered that I could use JavaScript in the backend too. This led me to learn Node.js and MongoDB. After that, I learned about single-page applications and decided to learn ReactJS. I built another live project using the MERN stack and TypeScript.

So, this is the sequence in which I learned web development:

1. HTML/CSS
1. JavaScript
1. Git
1. SCSS
1. Node.js
1. MongoDB
1. React
1. TypeScript

### [Initial Setup for Next.js Projects](https://ayushchugh.com/blog/initial-setup)

Published: 2024-09-13

A guide to setting up your Next.js project efficiently.

## The Problem

Starting a new project can be daunting, especially when it involves setting up a robust environment for Next.js. You may find yourself overwhelmed with the myriad of configurations and tools available. How do you ensure that your project is set up efficiently and follows best practices from the get-go?

## The Solution

The solution is straightforward: set up your project in a way that encourages scalability and maintainability without overcommitting to complex configurations initially. Think of it as laying a solid foundation upon which you can build incrementally.

## Step-by-Step Guide

### 1. Setting Up the Project

First, create a new Next.js project. If you don't have Node.js installed, you'll need to do that first.

```bash
npx create-next-app@latest my-nextjs-app
cd my-nextjs-app
```

This command sets up a new Next.js project with all the necessary files and folders. It's your starting point for building a scalable application.

### 2. Configuring the Environment

Next, set up your environment variables. Create a .env.local file in the root of your project:

```bash
# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
```

Environment variables help keep sensitive data secure and configurable across different environments (development, staging, production).

### 3. Organizing Your Folder Structure

--- https://stackoverflow.com/questions/76214501/nextjs-13-folder-structure-best-practice ---

A well-organized folder structure makes your project easier to navigate and maintain. Here’s a suggested structure:

```bash
/app
  layout.jsx
  /(auth)
  	layout.jsx
	/signup
		index.jsx
	/login
		index.jsx
/components
  Header.jsx
  Footer.jsx
/helpers
  api.js
/lib
  auth.js
/hooks
  useAuth.js
/context
  AuthContext.js
/styles
  globals.css
  Home.module.css
```

you can also add custom component library like MUI, Radix UI

you can also create a Private folders which can be created by prefixing a folder with an underscore: `_folderName`

### 4. Setting Up Linting and Formatting

To maintain code quality and consistency, set up ESLint and Prettier.

```bash
npm install --save-dev eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
```

Create an .eslintrc.json file:

```json
{
	"extends": [
		"eslint:recommended",
		"plugin:react/recommended",
		"plugin:react-hooks/recommended",
		"prettier"
	],
	"plugins": ["prettier"],
	"rules": {
		"prettier/prettier": "error"
	},
	"settings": {
		"react": {
			"version": "detect"
		}
	}
}
```

And a prettier.config.js file:

```js
module.exports = {
	singleQuote: true,
	trailingComma: "all",
};
```

### 5. Adding Essential Packages

Enhance your project with essential libraries:

```bash
npm install axios react-query @mui/material @emotion/react @emotion/styled
```

- Axios: For making API requests.
- React Query: For data fetching and state management.
- Material-UI: For UI components.

### 6. Setting Up API Calls with Axios

Create an api directory and an axiosInstance.js file:

```js
// api/axiosInstance.js
import axios from "axios";

const axiosInstance = axios.create({
	baseURL: process.env.NEXT_PUBLIC_API_URL,
});

axiosInstance.interceptors.request.use(
	config => {
		// Add any custom request interceptors here
		return config;
	},
	error => {
		return Promise.reject(error);
	}
);

axiosInstance.interceptors.response.use(
	response => {
		// Add any custom response interceptors here
		return response;
	},
	error => {
		return Promise.reject(error);
	}
);

export default axiosInstance;
```

### Final Thoughts

By following these steps, you’ll have a solid foundation for your React and Next.js project. This setup not only makes it easier to scale and maintain your application but also allows you to experiment and build incrementally without feeling overwhelmed.

Remember, the key to a successful setup is to start simple and expand as needed. Keep your configurations minimal and build upon them as your project grows. This approach will save you time and headaches in the long run, allowing you to focus on building great features.

## Optional

- [RSS](https://ayushchugh.com/rss): Blog feed.
- [Sitemap](https://ayushchugh.com/sitemap.xml): Every indexable URL on this site.
- [GitHub](https://github.com/aayushchugh): Code and open source. Username aayushchugh.
- [X](https://x.com/aayushchugh): Short updates. @aayushchugh.
- [LinkedIn](https://www.linkedin.com/in/aayushchugh/): Work history and contact.
- [Discord](https://discord.gg/wydHwZNu8W): Ravix Studio community.
- [Topmate](https://topmate.io/aayushchugh): Book a 1:1.
- [Email](mailto:hey@ayushchugh.com): hey@ayushchugh.com
