My Experience Building a Full-Stack App (Next.js + Express + MongoDB)
Somnath Baidya

Somnath Baidya @dessomu

About: I am trying to be a good software developer day by day.

Location:
Kolkata
Joined:
May 17, 2024

My Experience Building a Full-Stack App (Next.js + Express + MongoDB)

Publish Date: Nov 12
0 0

I am turning to full-stack from a frontend developer. As a React.Js developer, I wanted to stick with the javascript ecosystem. So, intuitively I picked Next.Js to smoothen the transition, reused some of my react code blocks to do so & built a few simple Node.Js apps.

Problems occurred as I built the frontend in Next.js and the backend with Express+MongoDB Compass, then deployed them on Vercel and Render separately.

I ) Deployment Failure Due to Local MongoDB Setup :

The Express + MongoDB application was deployed on Render, but it was still using a local MongoDB Compass setup instead of a cloud database.

As Render (and services like Vercels) are designed to connect to hosted databases, the deployment failed immediately.

Resolve :

I learned about the issue and created a MongoDB Atlas cluster, connected it with MongoDB Compass using the atlas connection string to access and manage the database in the cloud.

II ) Incorrect Cookie Handling in Next.js :

Initially, cookies were being set and verified in Next.js rather than in the Express backend. Since both environments were completely separate, it caused conflicts.

A quick recap :

  • Only the backend should create and remove secure cookies (httpOnly, secure, sameSite).

  • The frontend should not handle sensitive cookie logic due to potential XSS attack risks.

So the ideal way is to set and clear cookies from the backend during login and logout, while the frontend simply managed routing logic.

Also I was trying to verify cookie in Next.js Middleware, which runs in an Edge Runtime environment — not Node.js.

This environment can only check if a cookie exists, not verify it.

Resolve :

  • Removed app/api/session.

  • Only conditional redirection logic (if-else checks) was kept in middleware.js.

    export function middleware(req) {
      const session = req.cookies.get("session_marker")?.value;
      const path = req.nextUrl.pathname;
    
      if (!session && path !== "/login") {
        return NextResponse.redirect(new URL("/login", req.url));
      }
    
      if (session && path === "/login") {
        return NextResponse.redirect(new URL("/home", req.url));
      }
    
      return NextResponse.next();
    }
    

III ) Cross-Origin Cookie Issue :

New issue appeared as I deployed them.

While testing locally, both frontend and backend shared the same origin, so cookies persisted correctly.

But, in production:

  • Frontend : deployed on vercel.app.

  • Backend : deployed on onrender.com.

In production when a user logged in, the backend generated a cookie correctly using the firebase token. The cookie was visible in the browser’s DevTools for a fraction of sec but disappeared immediately as the page reloaded.

Now the user is redirected back to the login page, as if never authenticated.

// Login (set)
res.cookie("auth_token", token, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",  
  sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
  maxAge: 7 * 24 * 60 * 60 * 1000, // expires in 7 days
  path: "/",
});

// Logout (clear)
res.clearCookie("auth_token", {
  httpOnly: true,
  secure: process.env.NODE_ENV==="production",
  sameSite:process.env.NODE_ENV==="production"? "none" : "lax",
  path: "/",
});
Enter fullscreen mode Exit fullscreen mode

Attempts to fix the issue :

  • Using CHIPS (Cookies Having Independent Partitioned State).

  • Setting domain attributes for cookies explicitly.

    But that won’t work, as vercel.app & onrender.com are completely different top-level domains

    =
    res.cookie("auth_token", token, {
      ....
      partitioned: true, // CHIPS
      domain: "https://mern-todo-8f4w.onrender.com/",
    });
    
  • Adding proxy routes in next.config.js to simulate same-origin behaviour, which worked only on development.

    /** @type {import('next').NextConfig} */
    const nextConfig = {
      async rewrites() {
        return [
          {
            source: "/api/:path*",
            destination: "https://mern-todo-8f4w.onrender.com/:path*", 
          },
        ];
      },
    };
    
    module.exports = nextConfig;
    

The Fact :

Cross-origin cookies between different deployment domains are unreliable and often blocked by browsers.

Implementing a Hybrid JWT + Session Authentication :

Here is how it works,

1. Login request :

  • The frontend sends a Firebase token to the backend.

  • The backend verifies it using firebase-admin.

  • A JWT is signed with verified user details.

  • A sessionId is generated using crypto.

  • Both JWT and sessionId are sent back to the frontend, and the session is stored in the database.

2. Frontend handling :

  • JWT is saved in localStorage.

  • A client-side cookie is created containing the sessionId, used only for routing logic in middleware.

3. Middleware routing :

  • Checks if the sessionId cookie exists.

  • Redirects the user to the home page if it does, otherwise to the login/signup page.

4. Authenticated requests :

  • Every request includes both JWT (from localStorage) and sessionId (from cookies) in the headers.

5. Authentication in backend :

  • The JWT is verified.

  • The sessionId is checked against the database.

  • If both are valid, the user data is attached to the request and proceed to server.

I learned that cross-origin setups rarely have a conventional fix — you often need to experiment, blending a few ideas to obtain the right fit that works for you.

Comments 0 total

    Add comment