<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adwik Gupta</title>
    <description>The latest articles on DEV Community by Adwik Gupta (@adwik_gupta).</description>
    <link>https://dev.to/adwik_gupta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3792383%2F650780fa-8adf-4d55-bd04-7b6c09f6abfd.jpg</url>
      <title>DEV Community: Adwik Gupta</title>
      <link>https://dev.to/adwik_gupta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adwik_gupta"/>
    <language>en</language>
    <item>
      <title>PockIt: The Voice-Activated Personal Expense Tracker</title>
      <dc:creator>Adwik Gupta</dc:creator>
      <pubDate>Sat, 28 Feb 2026 23:32:37 +0000</pubDate>
      <link>https://dev.to/adwik_gupta/pockit-the-voice-activated-personal-expense-tracker-ahm</link>
      <guid>https://dev.to/adwik_gupta/pockit-the-voice-activated-personal-expense-tracker-ahm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The MERN stack is one of the most widely used full-stack development stacks and for good reason. It is easy to learn, highly scalable and powerful enough to build production-grade applications.&lt;br&gt;
I built PockIt, a personal expense tracker using the MERN stack but with a unique feature:&lt;br&gt;
Users can log expenses using voice instead of typing.&lt;br&gt;
Manually entering expenses is tedious. Most people start tracking their spending but stop after a few days because the process becomes inconvenient.&lt;br&gt;
PockIt allows users to speak their expenses, which are converted into structured transaction data using LangChain and an LLM, and stored in MongoDB.&lt;br&gt;
The project has four main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React – Frontend&lt;/li&gt;
&lt;li&gt;Firebase – Authentication&lt;/li&gt;
&lt;li&gt;Node.js – Backend&lt;/li&gt;
&lt;li&gt;MongoDB + LangChain – Database and AI processing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s break down how each part works.&lt;/p&gt;


&lt;h2&gt;
  
  
  Project File Structure
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, here is a high-level look at how the frontend and backend are organized to keep the codebase modular and scalable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📁 pockit/
├── 📁 backend/
│   ├── 📁 controllers/
│   │   └── 📄 transaction.js
│   ├── 📁 db/
│   │   └── 📄 connect.js
│   ├── 📁 middleware/
│   │   └── 📄 authMiddleware.js
│   ├── 📁 models/
│   │   ├── 📄 user.js
│   │   └── 📄 transaction.js
│   ├── 📁 routes/
│   │   ├── 📄 transactionsRouter.js
│   │   └── 📄 aiRouter.js
│   └── 📄 app.js
│
└── 📁 frontend/
    ├── 📁 src/
    │   ├── 📁 axios/
    │   │   └── 📄 api.jsx
    │   ├── 📁 components/
    │   │   ├── 📄 Header.jsx
    │   │   ├── 📄 Transaction.jsx
    │   │   ├── 📄 AddTransaction.jsx
    │   │   └── 📄 Profile.jsx
    │   ├── 📁 pages/
    │   │   ├── 📄 register.jsx
    │   │   ├── 📄 login.jsx
    │   │   ├── 📄 HomePage.jsx
    │   │   ├── 📄 Home.jsx
    │   │   └── 📄 Dashboard.jsx
    │   ├── 📄 App.jsx
    │   ├── 📄 firebase.js
    │   ├── 📄 index.css
    │   └── 📄 main.jsx
    ├── 📄 package.json
    └── 📄 vite.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  React – The Frontend
&lt;/h2&gt;

&lt;p&gt;React is a component-based JavaScript library used for building interactive user interfaces.&lt;br&gt;
React applications are typically Single Page Applications (SPAs). This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The browser loads only one HTML file&lt;/li&gt;
&lt;li&gt;JavaScript handles navigation&lt;/li&gt;
&lt;li&gt;The page does not reload when switching views&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This results in a faster and smoother user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How React Works Internally&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;React uses something called the Virtual DOM.&lt;br&gt;
Flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;React creates a Virtual DOM in memory&lt;/li&gt;
&lt;li&gt;When state changes, a new Virtual DOM is created&lt;/li&gt;
&lt;li&gt;React compares the old and new Virtual DOM (Reconciliation)&lt;/li&gt;
&lt;li&gt;Only the changed elements are updated in the real DOM&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes updates efficient and fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend Architecture in PockIt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most important component in my frontend is: &lt;code&gt;&amp;lt;Transactions /&amp;gt;&lt;/code&gt;&lt;br&gt;
It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching transactions from the backend&lt;/li&gt;
&lt;li&gt;Deleting transactions&lt;/li&gt;
&lt;li&gt;Sorting and filtering transactions&lt;/li&gt;
&lt;li&gt;Rendering &lt;code&gt;&amp;lt;AddTransaction /&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;AddTransaction /&amp;gt;&lt;/code&gt; component handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual transaction entry&lt;/li&gt;
&lt;li&gt;Voice-based transaction entry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;API Communication using Axios Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of attaching the authentication token manually in every request, I created a custom API client using axios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
});

apiClient.interceptors.request.use(async (config) =&amp;gt; {
  const user = auth.currentUser;
  if (user) {
    const token = await user.getIdToken();
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures every request automatically includes the Firebase ID token.&lt;br&gt;
This pattern keeps the code clean and secure.&lt;/p&gt;


&lt;h2&gt;
  
  
  Firebase – Authentication
&lt;/h2&gt;

&lt;p&gt;Instead of building authentication from scratch, I used Firebase Authentication.&lt;/p&gt;

&lt;p&gt;Firebase handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User registration&lt;/li&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Session management&lt;/li&gt;
&lt;li&gt;Token generation and validation
I used Email/Password authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authentication Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: User Login&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User logs in using:&lt;br&gt;
&lt;code&gt;createUserWithEmailAndPassword(auth, email, password);&lt;/code&gt;&lt;br&gt;
&lt;code&gt;signInWithEmailAndPassword(auth, email, password);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Firebase verifies credentials securely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Token Generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Firebase generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ID Token (JWT) – valid for 1 hour&lt;/li&gt;
&lt;li&gt;Refresh Token – used to generate new ID tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ID token contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User ID&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Digital signature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Sending Token to Backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The frontend sends the token:&lt;br&gt;
&lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Backend Verification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Backend verifies the token using Firebase Admin SDK:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const decodedToken = await admin.auth().verifyIdToken(token);&lt;/code&gt;&lt;br&gt;
This ensures the request is authenticated.&lt;/p&gt;

&lt;p&gt;Firebase is initialized in: &lt;code&gt;firebase.js&lt;/code&gt;&lt;br&gt;
Backend uses: &lt;code&gt;serviceAccount.json&lt;/code&gt;&lt;br&gt;
for secure verification.&lt;br&gt;
This removes the need to manage passwords or sessions manually.&lt;/p&gt;


&lt;h2&gt;
  
  
  Node.js – Backend Architecture
&lt;/h2&gt;

&lt;p&gt;Node.js is a JavaScript runtime built on Chrome’s V8 engine.&lt;/p&gt;

&lt;p&gt;Important clarification:&lt;/p&gt;

&lt;p&gt;Node.js is single-threaded for JavaScript execution, but it uses libuv and the OS to handle asynchronous operations efficiently.&lt;/p&gt;

&lt;p&gt;This allows Node.js to handle thousands of concurrent requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Flow in PockIt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example endpoint: &lt;code&gt;/api/transactions&lt;/code&gt;&lt;br&gt;
In app.js: &lt;code&gt;app.use('/api/transactions', transactionsRouter);&lt;/code&gt;&lt;br&gt;
In router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.route('/')
  .get(authMiddleware, getTransactions)
  .post(authMiddleware, createTransaction);

router.route('/:id')
  .delete(authMiddleware, deleteTransaction);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request arrives at router&lt;/li&gt;
&lt;li&gt;authMiddleware verifies token&lt;/li&gt;
&lt;li&gt;Controller processes request&lt;/li&gt;
&lt;li&gt;Response sent back&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Authentication Middleware&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is one of the most important parts of the backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const decodedToken = await admin.auth().verifyIdToken(token);

const { uid, email, name, phone_number } = decodedToken;

let user = await User.findOne({ firebaseUid: uid });

if (!user) {
  user = new User({
    firebaseUid: uid,
    email,
    displayName: name || '',
    ...(phone_number &amp;amp;&amp;amp; { phone_number })
  });

  await user.save();
}

req.user = user;
next();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This middleware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifies Firebase token&lt;/li&gt;
&lt;li&gt;Finds user in database&lt;/li&gt;
&lt;li&gt;Creates user if not exists&lt;/li&gt;
&lt;li&gt;Attaches user to request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures every transaction belongs to a valid user.&lt;br&gt;
This is a production-grade authentication pattern.&lt;/p&gt;


&lt;h2&gt;
  
  
  MongoDB – Database Design
&lt;/h2&gt;

&lt;p&gt;MongoDB is a NoSQL document database.&lt;br&gt;
It stores data in JSON-like documents.&lt;br&gt;
Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexible schema&lt;/li&gt;
&lt;li&gt;Easy scaling&lt;/li&gt;
&lt;li&gt;Fast development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connected using Mongoose at server startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Schema&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const UserSchema = new mongoose.Schema({
  firebaseUid: {
    type: String,
    required: true,
    unique: true,
    index: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  displayName: String,
  phone_number: {
    type: String,
    unique: true,
    sparse: true
  }
}, { timestamps: true });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transaction Schema&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const transactionSchema = new mongoose.Schema({
  title: String,
  amount: Number,
  type: {
    type: String,
    enum: ['income', 'expense'],
  },
  category: String,
  date: Date,

  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    index: true,
  }
}, { timestamps: true });

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each transaction is linked to a specific user.&lt;br&gt;
This ensures proper data isolation.&lt;/p&gt;


&lt;h2&gt;
  
  
  LangChain – Voice to Structured Data
&lt;/h2&gt;

&lt;p&gt;This is the core feature of PockIt.&lt;br&gt;
User input:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Spent 800 rupees on auto yesterday&lt;br&gt;
Voice → Text → LangChain → Structured JSON&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Prompt Template&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const prompt = ChatPromptTemplate.fromMessages([
  ['system', 'You are an expert at extracting transaction data from user text. ' +
        "Today's date is {currentDate}. " +
        'You must extract the amount, vendor, category, and date. ' +
        'If the user mentions spending, the type is "expense". ' +
        'If the user mentions "credit", "salary", or "received", the type is "income". ' +
        `If type is "income", the category MUST be one of: ${incomeCategories.join(', ')}. ` +
        `If type is "expense", the category MUST be one of: ${expenseCategories.join(', ')}. ` +
        'If no specific category is mentioned, use "Other". ' +
        'If the user says "today", use {currentDate}. If they say "yesterday", calculate and use the previous day\'s date in YYYY-MM-DD format. ' +
        'If no date is mentioned, the date field must be {currentDate} in YYYY-MM-DD format.'
  ],
  ['human', '{inputText}'],
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures consistent extraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output Validation using Zod&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This ensures structured output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const transactionSchema = z.object({
  type: z.enum(['expense', 'income']),
  amount: z.number(),
  category: z.enum(allCategories),
  vendor: z.string().nullable(),
  date: z.string().nullable(),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents invalid AI output from entering the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Output&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "type": "expense",
  "amount": 800,
  "category": "Transport",
  "vendor": "auto",
  "date": "2026-02-23"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structured data is saved in MongoDB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete System Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;User logs in using Firebase&lt;/li&gt;
&lt;li&gt;User speaks expense&lt;/li&gt;
&lt;li&gt;Voice converted to text&lt;/li&gt;
&lt;li&gt;Text sent to backend&lt;/li&gt;
&lt;li&gt;LangChain extracts structured data&lt;/li&gt;
&lt;li&gt;Backend saves transaction&lt;/li&gt;
&lt;li&gt;React updates UI instantly&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Key Engineering Learnings
&lt;/h2&gt;

&lt;p&gt;Building PockIt helped me understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure authentication using Firebase tokens&lt;/li&gt;
&lt;li&gt;Middleware-based backend architecture&lt;/li&gt;
&lt;li&gt;Database schema design for multi-user systems&lt;/li&gt;
&lt;li&gt;Safe integration of LLMs using validation&lt;/li&gt;
&lt;li&gt;Clean separation between frontend, backend, and AI layers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, I learned that improving user experience (voice input) can significantly improve usability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;PockIt started as a simple MERN project but evolved into a full system combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend engineering&lt;/li&gt;
&lt;li&gt;Backend architecture&lt;/li&gt;
&lt;li&gt;Authentication systems&lt;/li&gt;
&lt;li&gt;Database design&lt;/li&gt;
&lt;li&gt;AI integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voice-based expense tracking removes friction.&lt;br&gt;
And removing friction is often the difference between a tool people try and a tool people actually use.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>node</category>
    </item>
  </channel>
</rss>
