Why did you choose the MERN stack specifically for this project?
▾
The MERN stack (MongoDB, Express, React, Node.js) uses
JavaScript/TypeScript across the entire application — one language
from database to browser. This reduces cognitive overhead, allows
code reuse for types and interfaces, and has a massive ecosystem
of packages. MongoDB's document model maps naturally to product
catalogs with varying attributes. React's component model suits a
design-heavy e-commerce UI. And Node.js handles concurrent API
requests well for a shopping platform. It is also one of the most
in-demand stacks in the Nigerian tech job market right now.
How does your application handle security — specifically
authentication?
▾
We implement JWT (JSON Web Tokens) for stateless authentication.
When a user logs in, the server signs a token with a secret key
using HS256 algorithm and returns it. The client stores this token
in localStorage and sends it in every subsequent request as a
"Bearer" header. On the server, our "protect" middleware verifies
the token signature before allowing access to any protected
endpoint. Passwords are never stored in plain text — we use
bcryptjs with a salt factor of 10 to hash all passwords before
saving to MongoDB. Admin routes have a second middleware layer
that checks the user's isAdmin flag.
Why TypeScript over plain JavaScript? What value did it add?
▾
TypeScript adds static type checking at compile time — catching
bugs before they reach production. For example, if our API returns
a product with a "price" field but our React component tries to
use "cost", TypeScript throws an error immediately rather than a
silent bug at runtime. We defined shared interfaces for all our
data shapes — IUser, IProduct, IOrder — which enforces consistency
between our Mongoose models and our React components. TypeScript
also makes the codebase self-documenting: a new team member can
understand exactly what data a function expects just by reading
its signature. Every TypeScript-specific line in our codebase has
an inline comment explaining it — making it pedagogically clear
for students learning TypeScript for the first time.
How does your app perform on slow Nigerian internet connections?
▾
We addressed this in several ways. First, all images are served
from Cloudinary's global CDN which has edge nodes closer to
African users, dramatically reducing image load times compared to
serving from a UK or US server. Second, we use React's lazy() and
Suspense to split the JavaScript bundle — instead of loading all 9
pages at once, only the page the user is on gets loaded. This cuts
the initial bundle size significantly. Third, all product images
use loading="lazy" so off-screen images don't block the initial
render. Fourth, the shopping cart is persisted in localStorage so
users don't lose their cart if their connection drops. These
optimizations make the app viable on 3G connections.
What makes this different from just using Shopify or
WooCommerce?
▾
Shopify and WooCommerce are excellent platforms but they have
significant limitations for the Nigerian market and for our
learning objectives. First, Shopify charges monthly fees of
$29–$299 USD plus transaction fees — that's ₦45,000–₦460,000
monthly just for the platform, before hosting costs. Building
custom means no recurring platform fees. Second, customisation is
constrained on Shopify — you cannot change the checkout
architecture or add custom backend logic without expensive apps.
Our platform is 100% ours to extend. Third, and most importantly
for this project: building from scratch demonstrates that we
understand every layer of a web application — from database schema
design to JWT middleware to React state management — which a
hosted solution completely hides.
How would you scale this if you had 10,000 users hitting it
simultaneously?
▾
Several scaling strategies would be needed. At the database level:
MongoDB Atlas supports horizontal sharding and read replicas —
we'd enable read replicas so database reads (browsing products)
don't compete with writes (placing orders). At the server level:
we'd move from a single Render instance to a load-balanced cluster
behind Nginx. We'd introduce Redis for caching frequently-accessed
data like the product catalogue — most users view the same 6
products, so caching API responses reduces database load by ~80%.
We'd add a message queue (like Bull with Redis) for order
processing so a traffic spike doesn't cause timeout errors. The
frontend on Vercel already scales infinitely since it's a static
CDN — that's not a bottleneck. We'd also implement rate limiting
on the API to prevent abuse.
What would you add if you had 3 more months?
▾
The most impactful additions for the Nigerian market would be: (1)
Paystack integration — Nigeria's leading payment gateway — to
enable direct card payments, bank transfers, and USSD payments
without leaving the site. Currently we support Cash on Delivery
and e-Money which are placeholders. (2) Product search with
full-text search and filters by price range and category. (3)
Product reviews and star ratings — social proof is critical for
high-value purchases online. (4) SMS order notifications via
Termii or Twilio Africa — Nigerians respond better to SMS than
email. (5) A wishlist/save for later feature. (6) Inventory
management — track stock levels and automatically show "Out of
Stock" badges. (7) Progressive Web App (PWA) capabilities so users
can install it on their phone home screen.
Explain how Context API works in your project and why you chose
it over Redux.
▾
React's Context API is a built-in solution for sharing state
across components without prop drilling (passing data through many
layers of components). We have two contexts: AuthContext manages
the logged-in user state — any component can call useAuth() to get
the current user, log in, or log out without needing to pass those
functions down through props. StoreContext manages the shopping
cart and product data globally. We chose Context API over Redux
because our state requirements are straightforward — we have two
bounded domains (auth and cart) with simple operations. Redux adds
significant boilerplate (actions, reducers, selectors, middleware)
that would be over-engineering for this scale. Context API,
combined with useState and useEffect hooks, provides everything we
need with far less code and complexity. If the app grew to 50+
components with complex shared state and frequent updates, Redux
Toolkit would become worth the trade-off.
What is the role of Cloudinary in your project — couldn't you
just store images in MongoDB?
▾
MongoDB is a document database optimised for structured data —
storing binary image files in it as Base64 strings would massively
bloat the database, slow down every query, and make the data hard
to back up. MongoDB has a 16MB document size limit which a single
high-res product image could approach. Cloudinary is purpose-built
for media — it stores images on a global CDN, automatically
compresses and optimises them, and serves them from the edge node
nearest to the user. It also provides free image transformations:
you can request a 200x200 thumbnail of a 4K image by changing the
URL parameters, without storing multiple versions. In our
architecture, we upload the image file to Cloudinary and store
only the returned URL string in MongoDB — giving us the best of
both worlds.
How does your admin dashboard prevent unauthorised access?
▾
Access is protected at two independent layers. On the frontend:
the AdminRoute component checks the AuthContext for a user with
isAdmin: true before rendering any admin page — non-admins are
redirected to the homepage. However, frontend checks alone are
never enough security. On the backend: every single admin API
endpoint (/api/admin/*) runs through two middleware functions in
sequence — first "protect" which verifies the JWT token and loads
the user from the database, then "admin" which checks user.isAdmin
=== true. Even if someone bypassed the frontend check (by directly
calling our API with curl or Postman), the server would still
return a 403 Forbidden response. This double-layer approach —
client guard plus server guard — is the industry standard for RBAC
(Role-Based Access Control).