Back to blogs

Freelance

How I Built My First Freelance Project with the MERN Stack

Last month I got my first freelance project — a journal publication platform. Here's the full breakdown: database design, system architecture, deployment, and what I learned.

By Pavan Kumar|March 15, 2026| 5 min read
  • #mern
  • #freelance
  • #system-design
  • #nextjs
  • #typescript
  • #deployment

Last month I got my first freelance project — one I'd been waiting for a long time. It came through my friend's sister's friend's uncle 😄 — a long chain of connections I never expected to lead anywhere.

The project is a journal publication platform. Authors submit their research manuscripts through the website, the editorial board (experts in the relevant field) reviews them, and if accepted, the paper gets published on the site.

Here's the flow I followed to build it from start to finish.

Step 1 — Database Design

Before writing a single line of code, I mapped out the entire database on Miro. This was my mental model — getting clarity on how the data connects before touching the keyboard. The system has 6 collections: users, admin, journals, manuscripts, editorial board, volumes, and issues:

Wise Publications DB Design on Miro
Wise Publications DB Design

Step 2 — System Design

Before writing code I had to decide: how many applications do I need?

This project has two very different types of users — the public (authors, readers) and the admin team (editors, reviewers, admins). Mixing them into one app would mean complex role-based routing, shared state, and a bloated codebase. So I split it into three separate applications: • publishers-backend — one API server, serves both frontends • publishers-website — public-facing Next.js site for authors and readers • publishers-admin — private Next.js dashboard for the admin team

This way the admin panel can go down or be updated without affecting the public site. The backend is the single source of truth — both frontends just consume it.

Backend Structure

The backend is built with TypeScript + Express.js, following a modular architecture. Every feature lives in its own folder with the same 5 files: route → controller → service → schema → validation. This means when something breaks, you know exactly where to look.

publishers-backend/plaintext
publishers-backend/
└── src/
    ├── config/
    │   └── db_connect.ts         # MongoDB connection
    ├── helpers/
    │   └── aws-s3.ts             # S3 file upload helper
    ├── middlewares/
    │   ├── auth.middleware.ts     # JWT verification
    │   ├── access.middleware.ts   # Role-based access control
    │   ├── async.middleware.ts    # Async error wrapper
    │   └── error.middleware.ts    # Global error handler
    ├── modules/
    │   ├── auth/                  # User register, login, JWT
    │   ├── admin/                 # Admin login, management
    │   ├── journals/              # CRUD for journals
    │   ├── manuscript/            # Submit, review, status flow
    │   ├── board_member/          # Editorial board assignment
    │   ├── volume_issue/          # Volumes and issues
    │   ├── dashboard/             # Stats for admin panel
    │   └── contact/               # Contact form
    └── utils/
        ├── constants.ts
        └── error_codes.ts

Each module follows the same pattern:

journals/plaintext
journals/
├── journal.route.ts        # Define endpoints
├── journal.controller.ts   # Handle request/response
├── journal.service.ts      # Business logic + DB queries
├── journal.schema.ts       # Mongoose schema
├── journal.types.ts        # TypeScript types
└── journal.validation.ts   # Input validation

Why this structure? Because when you're building fast, you need to know where everything is without thinking. Route broke? → journal.route.ts. Wrong data returned? → journal.service.ts. Bad input accepted? → journal.validation.ts. No hunting around.

Frontend — Public Website

The public website is built with Next.js App Router + TypeScript + Tailwind CSS. It's what authors and readers see.

publishers-website/plaintext
publishers-website/
├── app/                          # Pages (App Router)
│   ├── page.tsx                  # Home
│   ├── journals/
│   │   ├── page.tsx              # All journals listing
│   │   └── [slug]/page.tsx       # Single journal detail
│   ├── submit-manuscript/        # Manuscript submission form
│   ├── login/                    # Author login
│   ├── guidelines/               # Author & reviewer guidelines
│   └── policies/                 # Privacy, terms, withdraw policy
├── components/
│   ├── common/                   # Button, Input (reusable)
│   ├── layout/                   # Navbar, Footer
│   └── shared/                   # InfoPageLayout (shared page wrapper)
├── features/
│   ├── journals/                 # JournalCard, SingleJournalMain
│   └── manuscripts/              # SubmitManuscriptForm
├── hooks/
│   └── useAuth.ts                # Auth state hook
├── libs/
│   ├── apiClient.ts              # Axios instance with base URL + auth headers
│   └── constants.ts
└── redux/
    └── store.ts                  # Global state

Admin Dashboard

The admin panel follows the same folder structure as the public website, with an added auth layer to protect all routes.


Once the database and system design were locked in, the rest was just focused coding. No more thinking about structure, no more refactoring decisions — just building. That's the whole point of planning upfront. When you know exactly where every file goes and how every piece connects, you stop second-guessing and start shipping.


Step 3 — Deployment

I deployed on a Hostinger VPS running Ubuntu. The backend runs on PM2, both frontends are containerized with Docker, Nginx handles the routing for all three apps, and Certbot takes care of the SSL certificates.


What I Learned

When I started this project, my goal was to be proper, not perfect.

The biggest lesson: it's always the plan, not the code. If your plan is solid, the coding is fast. The more time you invest in planning — the database, the system design, the folder structure — the less time you waste refactoring and second-guessing while building.

Understanding the client's requirements clearly and researching before you start is something I'd recommend to anyone — whether it's a freelance project or your own product. Don't rush to the code. Rush to clarity first.

This was my first freelance project. I wanted to give it a real try, and it worked. Now my focus is on showing more of my work publicly, getting better at what I build, and taking on more projects.

This blog is step one of that.