InStock

A fully responsive, full-stack inventory management system built with React, TypeScript, Node.js, and PostgreSQL.

View demo
InStock warehouse and inventory management web app preview.

<Overview>

InStock was originally a group project completed during a bootcamp where I learned some key aspects of collaborative development, such as Git flow, resolving merge conflicts, and using Jira to stay organized.

After graduating, I decided to rebuild the web app on my own to gain more hands-on experience with some of the areas that I didn’t get to work on the first time around. This also gave me the chance to switch up the tech stack a bit and implement some additional features such as:

  • Using TypeScript on the frontend
  • Moving to raw SQL with PostgreSQL (instead of Knex and MySQL)
  • Building custom form inputs that meet accessibility guidelines
  • Centralized error handling and logging
  • Automated database reseeding
  • Protecting API routes with rate limiting

<Toolkit>

  • React
  • TypeScript
  • SCSS
  • Node.js
  • Express
  • PostgreSQL
  • Supabase
  • Node-Cron
  • Winston
  • Figma

<Approach>

  • RESTful API
  • MVC Architecture
  • Database Migrations
  • Automated Seeding
  • Error Handling
  • Error Logging
  • Rate Limiting

<Metadata>

Role

Full Stack Developer

Completed

2025

API Routes

10+

Hosting

Netlify, Supabase, Render

Note: this demo is hosted using Render's free tier, so the initial load might take up to 60 seconds.

View demo

<Build Highlights>

Below are some highlights of the build that pushed me to explore new concepts in accessibility, backend architecture, and interactive UI patterns.

Building an accessible custom dropdown input.

I built a custom dropdown input from scratch that supports screen readers, keyboard navigation, and intuitive focus management.

Each option uses role='radio', aria-checked, and tabIndex='0' to communicate state to screen readers, while keyboard events handle navigation and selection.

Alt text here

To support keyboard navigation, I manage focus with a handler that responds to ArrowUp, ArrowDown, and Escape.

Alt text here

This was my first deep dive into accessibility, and it gave me a stronger appreciation for the work that goes into building inclusive UI components.

Formatting the phone number while a user types.

Continuing with the theme of custom inputs, I also built a phone number field that formats the value in real time as the user types.

Everything was working smoothly until I ran into an issue when editing a number in the middle of the input. Because controlled inputs re-render on each keystroke, the cursor kept jumping to the end of the string which is not ideal.

To fix this, I wrote a function to save the cursor position for each keystroke, then used useLayoutEffect to restore the selection position immediately after each update.

Alt text here

This ended up being one of those deceptively simple features that taught me how much thought it takes to get small UX details feeling smooth and intuitive.

Building a scalable backend.

I built the backend based on provided API specs by setting up routes, request handlers, and database queries to match the defined structure.

To keep things organized, I followed an MVC style pattern: routes handle incoming requests and pass them to controllers, which manage the business logic and communicate with models. The models run raw SQL queries directly against the database. This setup helped me get a clearer sense of how data flows through the app and where different responsibilities should live.

Alt text here

Centralizing error handling and implementing logging with Winston.

To make the backend feel more 'production-ready', I added centralized error handling middleware and integrated Winston for logging.

Since both were new to me, I spent some time digging into the docs to understand how they work and what problems they solve. The errorHandler middleware now catches errors across the app, and either logs information or returns user-friendly messages, depending on the environment.

Adding logging was a great experience and even helped me optimize my frontend API calls. While testing, I noticed frontend API calls we being triggered (even when the first one failed). Thinking this was odd, I looked into what was happening and cleaned up the logic so the second call only fired if the first one succeeded. Catching that in the logs helped me spot and fix a bug I might’ve missed otherwise!

Keeping the database clean with scheduled reseeds.

In an attempt to keep the demo data clean, I set up a scheduled reseed that runs every three days using node-cron.

Setting this up led me to explore how database connections are managed across scheduled scripts and the main application, which gave me a deeper understanding how of background tasks interact with a live system.

After deployment, I made a small update to account for server sleep behaviour by adding a startup check: if the last reseed was over 24 hours ago, the database refreshes automatically when the server wakes up.

Protecting the API with rate limiting

To protect the API from abuse, I added rate-limiting that allows 20 write operations per IP every 15 minutes. This helps protect the demo site from being flooded with requests without requiring users to sign in.

GET requests are excluded from this limit so users can explore freely.

Alt text here

<Retrospective>

Rebuilding this project gave me a much deeper understanding of full stack development and how all the moving parts fit together. It also gave me a chance to slow down and ask better questions. On the backend, that meant learning how to structure a codebase that's easy to maintain and debug. On the frontend, I explored custom UI elements and accessibility considerations that I haven't done before.

The space to figure things out properly was one of the valuable parts of this project. From learning how to preserve cursor position in a custom input, debugging API call flow using logs, or working with raw SQL and database connections, I came out of each challenge with a clearer sense of why things are built the way they are.