<?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: Hamid Karimi</title>
    <description>The latest articles on DEV Community by Hamid Karimi (@hkarimi).</description>
    <link>https://dev.to/hkarimi</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%2F3649553%2F8bb234d8-7527-48b2-b3e2-4d1421d34a61.png</url>
      <title>DEV Community: Hamid Karimi</title>
      <link>https://dev.to/hkarimi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hkarimi"/>
    <language>en</language>
    <item>
      <title>I Built an Open-Source School Management System API — SchoolOS</title>
      <dc:creator>Hamid Karimi</dc:creator>
      <pubDate>Mon, 23 Mar 2026 10:28:11 +0000</pubDate>
      <link>https://dev.to/hkarimi/i-built-an-open-source-school-management-system-api-schoolos-3e8b</link>
      <guid>https://dev.to/hkarimi/i-built-an-open-source-school-management-system-api-schoolos-3e8b</guid>
      <description>&lt;h2&gt;
  
  
  What is SchoolOS?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SchoolOS&lt;/strong&gt; is a fully production-ready School Management Information System (MIS) REST API built with Node.js, Express, TypeScript, and MongoDB. It covers everything a school needs to manage its operations through a clean, well-structured API.&lt;/p&gt;

&lt;p&gt;The project is fully open-source under the MIT license, anyone can use it, extend it, or contribute to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; Node.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework:&lt;/strong&gt; Express.js&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; MongoDB + Mongoose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; Zod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; JWT (Access + Refresh tokens with session management)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's included?
&lt;/h2&gt;

&lt;p&gt;SchoolOS ships with 12 fully tested modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Students&lt;/strong&gt; — CRUD, filtering, search, pagination&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teachers&lt;/strong&gt; — CRUD with subject and qualification tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classes&lt;/strong&gt; — Class creation, student enrollment, capacity management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timetable&lt;/strong&gt; — Weekly scheduling with teacher conflict detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attendance&lt;/strong&gt; — Single and bulk recording, student summary reports&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exams&lt;/strong&gt; — Exam scheduling with status tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grades&lt;/strong&gt; — Auto grade calculation (A+/A/B/C/D/F), bulk entry, summaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fees&lt;/strong&gt; — Payment recording, overdue tracking, student summaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Announcements&lt;/strong&gt; — Audience-targeted (all, teachers, students, parents)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messages&lt;/strong&gt; — Inbox, sent, unread count, read receipts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Library&lt;/strong&gt; — Book catalog, borrow/return with availability control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HR&lt;/strong&gt; — Staff management, contracts, salary summaries&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The project follows a clean flat layered architecture. Every feature is spread across exactly 5 files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;models/       → Mongoose schema and TypeScript interface
validators/   → Zod schemas for request validation
services/     → Business logic (database operations)
controllers/  → HTTP handlers (thin, no business logic)
routes/       → Express router with middleware applied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The single rule: &lt;strong&gt;controllers stay thin&lt;/strong&gt;. All database queries and business logic belong in services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Authentication is powered by a custom JWT system with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access token (short-lived, sent in Authorization header)&lt;/li&gt;
&lt;li&gt;Refresh token (long-lived, stored in httpOnly cookie)&lt;/li&gt;
&lt;li&gt;Session management with device tracking&lt;/li&gt;
&lt;li&gt;Role-based access control (admin, teacher, student, parent)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Response Format
&lt;/h2&gt;

&lt;p&gt;All endpoints follow a consistent response structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Operation successful"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hamidukarimi/SchoolOS-backend.git
&lt;span class="nb"&gt;cd &lt;/span&gt;SchoolOS-backend
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;The frontend is currently in development — a React + TypeScript + Tailwind CSS web app that consumes this API. It will be open-source as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔗 GitHub: &lt;a href="https://github.com/hamidukarimi/schoolos-backend" rel="noopener noreferrer"&gt;https://github.com/hamidukarimi/schoolos-backend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📄 License: MIT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you find it useful, a ⭐ on GitHub means a lot. Contributions are very welcome!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>node</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Production-Grade React Auth Starter (JWT, Refresh Tokens, Zustand, TanStack Query)</title>
      <dc:creator>Hamid Karimi</dc:creator>
      <pubDate>Wed, 11 Mar 2026 18:01:14 +0000</pubDate>
      <link>https://dev.to/hkarimi/building-a-production-grade-react-auth-starter-jwt-refresh-tokens-zustand-tanstack-query-3pk3</link>
      <guid>https://dev.to/hkarimi/building-a-production-grade-react-auth-starter-jwt-refresh-tokens-zustand-tanstack-query-3pk3</guid>
      <description>&lt;h1&gt;
  
  
  Building a Production-Grade React Auth Starter
&lt;/h1&gt;

&lt;p&gt;Authentication is one of those things every frontend developer rebuilds from scratch on every new project. I got tired of that — so I built &lt;strong&gt;authforge-client&lt;/strong&gt;: a clean, scalable React + TypeScript auth starter that handles all the hard parts properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hamidukarimi/authforge-client" rel="noopener noreferrer"&gt;GitHub →&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in the stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React + TypeScript&lt;/strong&gt; (Vite + SWC)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt; — server state and mutations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zustand&lt;/strong&gt; — global auth state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zod&lt;/strong&gt; — schema validation and TypeScript types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Hook Form&lt;/strong&gt; — form management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framer Motion&lt;/strong&gt; — animations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Axios&lt;/strong&gt; — HTTP client with interceptors&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tailwind CSS v4&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The folder structure is feature-based, not type-based:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├─ api/           # axios instance + endpoints
├─ features/
│  ├─ auth/       # login, register, schemas, hooks, services
│  └─ user/       # change password
├─ components/
│  ├─ ui/         # Button, Input, Alert, Spinner
│  └─ layout/     # Navbar, ProtectedRoute, PublicRoute
├─ store/         # Zustand auth store
├─ context/       # AuthContext (session restore)
├─ hooks/         # useAuth, useLogout
└─ utils/         # token helpers, error handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each feature owns its own components, hooks, services, schemas, and types. Adding a new feature never touches existing ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  The token refresh flow
&lt;/h2&gt;

&lt;p&gt;This is where most auth implementations cut corners. Here's the full flow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On login/register:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Backend returns &lt;code&gt;accessToken&lt;/code&gt; in the response body&lt;/li&gt;
&lt;li&gt;Backend sets &lt;code&gt;refreshToken&lt;/code&gt; as an &lt;code&gt;httpOnly&lt;/code&gt; cookie&lt;/li&gt;
&lt;li&gt;Frontend stores &lt;code&gt;accessToken&lt;/code&gt; in &lt;code&gt;sessionStorage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User object goes into Zustand&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;On every API request:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Axios request interceptor&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On 401 (expired access token):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Axios response interceptor&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Queue all failed requests&lt;/span&gt;
  &lt;span class="c1"&gt;// Call POST /api/token once&lt;/span&gt;
  &lt;span class="c1"&gt;// Retry all queued requests with new token&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The failed queue pattern is important — without it, multiple simultaneous 401s would trigger multiple refresh calls, causing race conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On page refresh:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ProtectedRoute calls restoreSession()&lt;/span&gt;
&lt;span class="c1"&gt;// restoreSession calls POST /api/token&lt;/span&gt;
&lt;span class="c1"&gt;// Gets fresh accessToken + user from refresh token cookie&lt;/span&gt;
&lt;span class="c1"&gt;// Repopulates Zustand&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The session restore problem
&lt;/h2&gt;

&lt;p&gt;One subtle bug I ran into: &lt;code&gt;isInitializing&lt;/code&gt; starts as &lt;code&gt;false&lt;/code&gt;, so on page refresh at &lt;code&gt;/dashboard&lt;/code&gt;, the &lt;code&gt;ProtectedRoute&lt;/code&gt; would see &lt;code&gt;isAuthenticated: false&lt;/code&gt; and immediately redirect to &lt;code&gt;/login&lt;/code&gt; — before &lt;code&gt;restoreSession&lt;/code&gt; even ran.&lt;/p&gt;

&lt;p&gt;The fix was a module-level flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sessionChecked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProtectedRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionChecked&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;sessionChecked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;restoreSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;restoreSession&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Never redirect until we've actually checked&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;sessionChecked&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isInitializing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Spinner&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Navigate&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Outlet&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike &lt;code&gt;useRef&lt;/code&gt;, a module-level variable persists across route changes — so navigating between protected routes doesn't trigger &lt;code&gt;restoreSession&lt;/code&gt; again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error handling done right
&lt;/h2&gt;

&lt;p&gt;Most tutorials just catch errors and show "Something went wrong." This starter parses errors properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// mutationFn catches raw AxiosError and throws ParsedError&lt;/span&gt;
&lt;span class="nx"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;registerService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nf"&gt;parseApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "Email already registered"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TanStack Query stores the parsed error, so the UI always shows human-readable messages from the backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multi-step registration form
&lt;/h2&gt;

&lt;p&gt;The register form is split into two steps with Framer Motion slide animations. The key challenge was cross-field validation (&lt;code&gt;password === confirmPassword&lt;/code&gt;) on Step 1 before allowing navigation to Step 2.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trigger(["confirmPassword"])&lt;/code&gt; alone doesn't work for cross-field Zod refinements — the fix was using &lt;code&gt;getValues()&lt;/code&gt; + &lt;code&gt;setError()&lt;/code&gt; manually in the &lt;code&gt;goNext&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;goNext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;step1Fields&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirmPassword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirmPassword&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manual&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Passwords do not match&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;setStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This starter is the auth foundation for &lt;strong&gt;Yummy&lt;/strong&gt; — a full-stack food ordering app I'm building. The backend is an ExpressJS + TypeScript server (also open source) with JWT, MongoDB sessions, role-based access control, and rate limiting.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;authforge-express&lt;/strong&gt; → &lt;a href="https://github.com/hamidukarimi/authforge-express" rel="noopener noreferrer"&gt;github.com/hamidukarimi/authforge-express&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;authforge-client&lt;/strong&gt; → &lt;a href="https://github.com/hamidukarimi/authforge-client" rel="noopener noreferrer"&gt;github.com/hamidukarimi/authforge-client&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this saves you time, drop a ⭐ on GitHub!&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built an Authentication System With Express.js, MongoDB, (Access/Refresh Tokens, Sessions, Rate Limiting &amp; More)</title>
      <dc:creator>Hamid Karimi</dc:creator>
      <pubDate>Sun, 22 Feb 2026 00:24:10 +0000</pubDate>
      <link>https://dev.to/hkarimi/i-built-an-authentication-system-with-expressjs-mongodb-accessrefresh-tokens-sessions-rate-hh0</link>
      <guid>https://dev.to/hkarimi/i-built-an-authentication-system-with-expressjs-mongodb-accessrefresh-tokens-sessions-rate-hh0</guid>
      <description>&lt;p&gt;I recently finished building one of my most solid backend projects — a complete authentication system written in Express.js, powered by MongoDB, JWT, and a clean architecture design.&lt;/p&gt;

&lt;p&gt;Here’s what I implemented step-by-step:&lt;/p&gt;

&lt;p&gt;**🔐 Access + Refresh tokens with secure storage&lt;/p&gt;

&lt;p&gt;🗂️ Session tracking in the database&lt;/p&gt;

&lt;p&gt;🧹 Automatic session invalidation&lt;/p&gt;

&lt;p&gt;🛡️ IP-based rate limiting (5 attempts / 10 mins)&lt;/p&gt;

&lt;p&gt;🧪 Validation middleware for all inputs&lt;/p&gt;

&lt;p&gt;⚠️ Global ApiError system for consistent error formatting&lt;/p&gt;

&lt;p&gt;🧱 Clean architecture with controllers, services, utils&lt;/p&gt;

&lt;p&gt;🛠️ Multiple bug fixes + edge case handling&lt;/p&gt;

&lt;p&gt;🚀 Focus on maintainability &amp;amp; production readiness**&lt;/p&gt;

&lt;p&gt;This project taught me a LOT about system design, real-world auth, and secure backend development.&lt;/p&gt;

&lt;p&gt;If you like it, Hit a ⭐ on GitHub or share it with other developers!&lt;/p&gt;

&lt;p&gt;👉 GitHub Repo: &lt;a href="https://github.com/hamidukarimi/authforge-express" rel="noopener noreferrer"&gt;https://github.com/hamidukarimi/authforge-express&lt;/a&gt;&lt;br&gt;
If you have ideas on improvements or want the front-end version too — let me know!&lt;/p&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>javascript</category>
      <category>backend</category>
    </item>
    <item>
      <title>👋 Hello Dev Community — I’m New Here!</title>
      <dc:creator>Hamid Karimi</dc:creator>
      <pubDate>Sat, 06 Dec 2025 18:50:18 +0000</pubDate>
      <link>https://dev.to/hkarimi/hello-dev-community-im-new-here-ln2</link>
      <guid>https://dev.to/hkarimi/hello-dev-community-im-new-here-ln2</guid>
      <description>&lt;p&gt;👋 Hello Dev Community — I'm New Here!&lt;/p&gt;

&lt;p&gt;Hi everyone! My name is Hamid, and this is my very first post on Dev.to 😊&lt;/p&gt;

&lt;p&gt;I'm a full-stack web and mobile app developer skilled in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React / Next.js&lt;/li&gt;
&lt;li&gt;React Native / Expo / Reanimated&lt;/li&gt;
&lt;li&gt;Node.js / Express&lt;/li&gt;
&lt;li&gt;MongoDB / MySQL&lt;/li&gt;
&lt;li&gt;UI/UX Design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I joined Dev.to because I want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share what I learn&lt;/li&gt;
&lt;li&gt;Connect with other developers&lt;/li&gt;
&lt;li&gt;Improve my skills&lt;/li&gt;
&lt;li&gt;Become part of the community&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm excited to start writing about the things I'm working on — especially React Native animations, mobile apps, and full-stack development.&lt;/p&gt;

&lt;p&gt;If you're reading this, feel free to say hi in the comments!&lt;br&gt;&lt;br&gt;
Looking forward to learning from all of you&lt;/p&gt;

&lt;p&gt;Thanks for having me here ❤️&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
