Architecture
System Diagram
┌─────────────────────────────────────────────────────────────────────────┐
│ myeasyguide.com │
│ Next.js 15 — Public Frontend │
│ │
│ Serves: Visitors, Travelers, Suppliers, Authors, Admins │
│ Port (dev): 3001 │
└──────────────┬──────────────────────────────┬───────────────────────────┘
│ │
│ NEXT_PUBLIC_API_BASE_URL │ NEXT_PUBLIC_POST_URL
│ (api/v1 proxied via │ (direct fetch to CMS)
│ Next.js rewrites) │
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────────────┐
│ api.myeasyguide.com │ │ cms-myeasyguide │
│ Express 5 — REST API │ │ Next.js 16 — CMS │
│ │ │ │
│ DB: walkthroughnepal │ │ DB: cms_myeasyguide │
│ (PostgreSQL) │ │ (PostgreSQL) │
│ │ │ │
│ ■ Activities │ │ ■ Posts CRUD │
│ ■ Users / Auth │ │ ■ Authors │
│ ■ Suppliers │ │ ■ Categories / Subcategories │
│ ■ Bookings / Payments │ │ ■ AI Blog Generation (Claude) │
│ ■ Reviews │ │ ■ Cities / Countries │
│ ■ Wishlists │ │ ■ Tags │
│ ■ Availability │ │ ■ Image Upload │
│ ■ Featured Tags │ │ ■ OpenAPI Spec │
│ ■ Newsletter │ │ │
│ ■ Media Library │ │ │
│ ■ Legal Documents │ │ │
│ ■ Recombee ML │ │ │
│ ■ Popularity Scoring │ │ │
└──────────────────────────────┘ └──────────────────────────────────────┘Data Flow
Guest-facing content
Activities, trips, suppliers, bookings, reviews — The frontend fetches all commerce data from
api.myeasyguide.comviaNEXT_PUBLIC_API_BASE_URL(defaulthttp://localhost:3000/api/v1). Next.jsrewrites()innext.config.tsproxy all/api/v1/*requests to the Express backend.Blog posts, travel guides — The frontend fetches content directly from
cms-myeasyguideviaNEXT_PUBLIC_POST_URL(defaulthttps://cms.myeasyguide.com). The CMS runs independently with its own database. Blog reading never touches the Express API.Search — The search page queries both backends in parallel: blogs from CMS (
/api/posts/published?search=...) and activities from Express API (/activity?search=...).Sitemap — During build,
next-sitemapfetches published post slugs from the CMS to include in the XML sitemap.
Admin content
Admin panels — The frontend has admin routes under
app/admin/that manage activities, suppliers, users, bookings, etc. via the Express API. The CMS has its own admin atapp/admin/for content management via CMS API routes.The two admin panels are separate — main platform admin (activities/commerce) lives in
myeasyguide.com/app/admin/, while content admin (posts/categories) lives incms-myeasyguide/app/admin/.
URL Routing
The frontend uses a catch-all route [...slug] with a sophisticated resolution chain:
Request: /nepal/pokhara/trekking/everest-base-camp-trek-123
1. Starts with "travel-guide"? No
2. Slug ends with "-{number}"? Yes → It's an activity
→ Fetch /api/v1/activity/123 from Express API
→ Render activity detail page
Request: /travel-guide/nepal/best-time-to-visit
1. Starts with "travel-guide"? Yes → It's a CMS post
→ Fetch /api/posts/{slug} from CMS
→ Render PostPageComponent
Request: /nepal/blog/best-time-to-visit
1. Starts with "travel-guide"? No
2. Slug ends with "-{number}"? No → Could be a blog
3. Try CMS first: getPostBySlug("best-time-to-visit")
→ If found, render PostPageComponent
4. If not found, fall back to Express API legacy resolve
5. Not found? → 404Database Independence
The API and CMS use completely separate PostgreSQL databases:
| Property | API | CMS |
|---|---|---|
| Database name | walkthroughnepal | cms_myeasyguide |
| Host | localhost | localhost |
| Port | 5432 | 5432 |
There are no cross-database foreign keys or direct database-level communication between them. They are independent systems that share the same Postgres instance but operate on separate databases.
Key Architectural Decisions
Why split the CMS from the API?
Originally, blog management (controllers, routes, validations) lived inside the Express API. As the content needs grew, this created problems:
- Blog CRUD competed with commerce endpoints for API resources
- Blog authors needed CMS-only features (rich text editing, scheduling, AI generation)
- The Express API's deployment cycle was tied to commerce changes
- Content editors had to authenticate through the same system as suppliers
The CMS was extracted as a standalone Next.js application with its own database, admin UI, and API layer. This separation allows:
- Independent deployment and scaling
- Tailored admin experience for content editors
- Dedicated AI generation pipeline (Claude batch API)
- No impact on commerce endpoints when deploying content changes
Why not a monolith?
The project started as a monolith but the editorial migration documented here is part of a larger effort to split concerns. Future migrations may include extracting supplier/activity management into the CMS or a dedicated admin app.