Skip to content

Local Test Launch

At this point, Voyagers Log is no longer just a collection of isolated parts.

We now have:

  • a MongoDB database running in Docker
  • an Express API running in Docker
  • Passport-based admin authentication
  • a Vite + Tailwind frontend running locally
  • a moderation workflow from public submission to admin approval

Now we need to verify that it works as one coordinated system.

This page is the local operations check.

Before we start stripping away local assumptions for deployment, we want to confirm that the application behaves properly in the environment it was built for.


At this stage, the local stack is deliberately split across two environments.

These services run as containers:

  • MongoDB
  • Express API

These are the backend services that represent the real application infrastructure.

This process runs directly on your machine:

  • Vite development server

We do that because Vite is a development-time tool, and running it natively gives:

  • faster startup
  • faster hot reload
  • simpler frontend development
  • less Docker noise while building the app
Not Everything Has to Be Containerized During Development

MongoDB and Express model the deployable backend system, so they benefit from containerization right away. Vite is primarily a development tool at this stage, so running it locally keeps the feedback loop faster and cleaner.


By now, our project should look roughly like this:

voyagers-log/
├── compose.yaml
├── .env
├── server/
│ ├── Dockerfile
│ ├── .dockerignore
│ ├── package.json
│ ├── index.js
│ ├── config/
│ │ └── passport.js
│ ├── middleware/
│ │ └── requireAuth.js
│ └── models/
│ ├── Log.js
│ └── User.js
└── client/
├── package.json
├── vite.config.js
├── index.html
└── src/
├── main.js
└── style.css

That is our local working convoy.


From the project root, start the Docker services:

Terminal window
docker compose up --build -d

This should:

  • build the API image if needed
  • start the MongoDB container
  • start the Express API container

If everything goes well, Docker Compose will bring the backend stack online in detached mode.


Step 2: Confirm the Containers Are Running

Section titled “Step 2: Confirm the Containers Are Running”

Run:

Terminal window
docker compose ps

You should see both services running:

  • db
  • api

If either service is not healthy or has exited, do not just stare at the terminal and hope for growth. Check the logs.


To inspect the API container:

Terminal window
docker compose logs api

You are looking for output along these lines:

Connected to MongoDB
Seeded default admin user
Voyager's Log API listening on port 3000

If you want to watch logs live:

Terminal window
docker compose logs -f api

That is especially handy while testing routes or login behavior.


To inspect MongoDB:

Terminal window
docker compose logs db

Or follow them live:

Terminal window
docker compose logs -f db

At this point, you mostly want confirmation that MongoDB started cleanly and has not crashed or failed initialization.


Before even touching the frontend, verify that the API is reachable.

Open a browser or use curl:

Terminal window
curl http://localhost:3000/api/health

You should get back something like:

{ "ok": true, "service": "voyagers-log-api" }

That tells you:

  • the API container is reachable from the host
  • Express is running
  • port mapping is working

If this route fails, the frontend is not your first problem.

Do Not Debug the Frontend Before Verifying the Backend

If the API is not reachable on localhost:3000, the frontend will fail in ways that look mysterious but are actually just downstream fallout. Check backend health first.


In a second terminal, move into the client folder:

Terminal window
cd client
npm run dev

Vite should start on:

http://localhost:5173

Now the frontend development server is alive.

At this point, your local environment is split like this:

  • Docker Compose → database + API
  • Vite dev server → frontend UI

That is our intended setup for local development.


Step 7: Open the Application in the Browser

Section titled “Step 7: Open the Application in the Browser”

Visit:

http://localhost:5173

You should see the Voyager’s Log interface with:

  • the public submission form
  • the admin login form
  • the approved voyage feed
  • the pending review panel

If the page loads but the data panels fail, that usually means the frontend is up but cannot communicate properly with the backend.

That leads us to the next important concept.


The browser is connected to the Vite dev server, not directly to Express.

So when your frontend code does this:

fetch('/api/voyages');

it is making a request to the Vite server first.

Then vite.config.js steps in and proxies that request to the API.

Your config should include this:

server: {
host: '0.0.0.0',
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}

That means:

  • browser hits http://localhost:5173/api/...
  • Vite forwards the request to http://localhost:3000/api/...
  • Express handles the route
  • the response comes back through Vite to the browser

This keeps the frontend code clean and avoids local cross-origin issues.

The Proxy Is the Bridge

During local development, the browser only knows it is talking to Vite. The proxy quietly forwards API traffic to Express so the frontend can keep using clean relative paths like /api/voyages.


Now test the public workflow.

In the browser:

  1. enter a voyager name
  2. enter a message
  3. submit the form

You should see a confirmation message indicating that the entry was submitted for review.

That means the frontend successfully:

  • captured form input
  • sent a request to /api/voyages
  • passed through the Vite proxy
  • hit the Express API
  • saved the entry to MongoDB as pending

Very respectable chain of custody.


Step 9: Verify That the Entry Is Not Public Yet

Section titled “Step 9: Verify That the Entry Is Not Public Yet”

After submitting a new entry, check the approved feed.

The new entry should not appear there yet.

That is correct behavior.

The public feed only displays entries whose status is:

approved

New public submissions are created as:

pending

That means the moderation layer is doing its job.


Now test the auth flow using the seeded development account:

username: admin
password: changeme123

After login succeeds:

  • the admin feedback area should confirm the login
  • the pending review queue should load
  • you should see any pending entries available for moderation

This verifies that:

  • Passport authentication is working
  • the session is being created
  • the browser is sending the session cookie on later requests

The login request is not enough on its own.

Once login succeeds, the browser must send the session cookie back on future admin requests so the server can recognize the authenticated user.

That is why the frontend uses:

credentials: 'include';

on authenticated fetch calls.

For example:

fetch('/api/admin/pending', {
credentials: 'include',
});

Without that, the API would treat the request like it came from a random unauthenticated stranger.

Which, to be fair, it effectively did.


In the pending review panel, click Approve on one of the pending voyage entries.

This should:

  • send a protected PATCH request to the API
  • update the entry’s status to approved
  • refresh the pending queue
  • refresh the approved public feed

If the approved feed updates correctly, that means the end-to-end moderation flow is working.

That is the full app loop:

  1. public user submits entry
  2. API stores it as pending
  3. admin logs in
  4. admin approves entry
  5. public feed displays it

Now we are cooking with actual stack behavior.


If you want to test the alternate moderation action, submit another entry and then click Hide instead of Approve.

That entry should:

  • disappear from the pending queue
  • remain absent from the public feed

That confirms the hidden state is also working correctly.


Before moving on, make sure all of the following are true:

  • docker compose ps shows both db and api running
  • http://localhost:3000/api/health responds successfully
  • http://localhost:5173 loads the frontend
  • public submission works
  • new entries are created as pending
  • admin login works
  • pending entries can be approved
  • approved entries appear in the public feed
  • hidden entries do not appear publicly

If those boxes are checked, the local system is doing exactly what it should.


Terminal window
docker compose ps
Terminal window
docker compose logs api
Terminal window
docker compose logs -f api
Terminal window
docker compose logs db
Terminal window
docker compose logs -f db
Terminal window
curl http://localhost:3000/api/health
Terminal window
docker compose down

Stop the backend stack and remove the named volume

Section titled “Stop the backend stack and remove the named volume”
Terminal window
docker compose down -v

That last command will wipe the database data too, so use it when you mean it.


Even though the app works beautifully now, it still has several local-development assumptions that are completely intentional at this stage.

The API still assumes MongoDB lives at:

mongodb://db:27017/voyagers_log

That only works inside Docker Compose.

The frontend still runs through Vite on port 5173.

Express is not yet serving compiled frontend assets.

Vite is still acting as the bridge for /api requests during development.

The seeded admin account and session secret are still development-friendly shortcuts, not production-grade configuration.

That is fine.

This page is about verifying the local stack, not pretending it is already deployment-ready.

Working Locally Does Not Mean Ready for Production

A healthy local stack proves the application is assembled correctly. It does not mean the app is ready to leave Docker Compose, accept hosted configuration, or serve production frontend assets yet.

Confidence Before Refactor

This local verification pass matters because once we begin refactoring for deployment, we want to know that any new breakage comes from deployment changes, not from unfinished local plumbing.


Completed Locally Running Voyagers Log Demo Repo


Now that the local stack is stable, our next job is to start removing the local assumptions that would break the moment we deploy.