Case study

Tour27: Travel Platform Built with NestJS and Next.js

Tour27 is a travel and tour discovery platform. People search listings and itineraries, send a lead, and the operator runs the catalog from an admin CMS. I owned it end to end while I was at ArhamSoft: the NestJS backend, the Next.js frontend, and the decisions about what rendered on the server. The live product is tour27.com. This is the build story, from the problem through the results I am willing to describe precisely.

Problem

Tour discovery looks like a content site until you try to operate it. Listings change, itineraries are structured rather than a blob of HTML, and a lead has to reach a person who can answer it with the right trip still attached. A theme on a generic CMS can show cards. It does not give you a clean API, a place for staff to edit a departure without breaking the public URL, or HTML that search engines can read without executing a client bundle.

The catalog also had two audiences who must not share a UI. Travelers want a fast public page and a form that does not lose their dates. Staff want an admin that can correct a listing, unpublish it, and see the lead that came in. Putting both in one client-rendered app makes the public pages heavy and the admin an afterthought, or the reverse. The product needed a real backend that both UIs could trust.

Ownership was the other constraint. This was not a feature dropped into someone else's platform. If the data model was wrong, I was going to live with it. That argues for modules with names from the business, listings, itineraries, and leads, instead of a single service file that grows a new conditional every week.

My role

I was the engineer responsible for the outcome, not a specialist bolted onto one layer. I designed the NestJS modules, the persistence behind them, and the Next.js routes that read them. I decided which public pages should be static or server-rendered and which admin screens should be ordinary authenticated React. I also dealt with the unglamorous path: validation on the lead form, the states where a listing is hidden, and the admin actions that must not 500 in front of staff.

Because I held both sides, tradeoffs stayed in one head. When the public page wanted a field the API did not have, I either added it to the module properly or cut it from the design. There was no long gap where the frontend invented a shape and the backend caught up later. That is the advantage of a full-stack owner on a product this size, and it is also the limit: I am describing the product I owned, not a cast of fifty.

Stack

The API is NestJS on Node.js, with TypeScript, organized as modules for the catalog and for leads. The public site and the admin shell are Next.js and React. Public listing and itinerary routes use server rendering or static generation so the primary content is HTML. The admin is an authenticated client of the same API. REST is the contract. I did not share a database connection with the browser, and I did not let the Next.js app import server entities directly. Delivery was a normal build pipeline for a Node service and a Next app, with environment-specific configuration kept out of the client bundle.

  • NestJS and Node.js for listings, itineraries, and leads
  • Next.js and React for the public site and admin
  • Server rendering and static generation on public routes
  • REST between the frontend and the API
  • TypeScript at the boundary so response shapes stay honest

Architecture

Each business noun became a Nest module with a controller that validates input, a service that holds rules, and a persistence layer that can change without a rewrite of the controller. Listings and itineraries are related, but a lead does not reach into listing tables on its own. It stores the identifier and the snapshot it needs to be answered later, so a catalog edit does not rewrite history.

On the Next.js side, public routes fetch on the server and send HTML. Interactive bits, such as a lead form, hydrate on the page that already has the trip context. The admin uses the API with a staff session. Unpublished listings are absent from public routes because the query says so, not because a CSS class hides them. That sounds small. It is the difference between a draft that leaks and a draft that does not.

I kept search and content concerns in the API even when Next could have queried a database itself. One write path means the admin and the public site cannot disagree about whether a tour is for sale. The cost is a network hop. The benefit is one place to fix a bug in the catalog rules.

Results

Tour27 shipped as a working travel platform: public discovery, itineraries, lead capture, and an admin CMS, on tour27.com. The architecture is still explainable. A new field on a listing has a module to live in. A public URL has a server-rendered page. Staff are not editing the database by hand to unpublish a trip. I am not going to attach a traffic or revenue figure I do not have a source for. The result I will claim is the one I was hired to produce: an end-to-end product with a modular NestJS API and a Next.js frontend that does not depend on the client bundle for its main content.

For someone hiring a Node or Next.js developer, Tour27 is the reference. It is smaller than REConnect and it was mine from the model to the page. If the product you have in mind looks like a catalog plus an internal tool plus a form that must not vanish, this is the shape I have already shipped.

tour27.com

Related services

Contact

Email aamirallidev@gmail.com or send a note from the contact section. I typically reply within one business day.