Next.js has API routes built in that act like a mini backend, and for many situations, they suffice. But if you look closer, you'll see that it doesn't have some of the flexibility and might of a full-fledged backend like Express.
So, why use Express over Next.js API routes? Well, Express is a mature backend framework, whereas Next.js API routes are essentially light-weight serverless functions. Here are a couple of things that Express does better:
File Handling: In Express, you could easily use res.download() to return files and download them. In Next.js API routes, you'd have to do it all manually and set headers yourself.
Middleware & Routing: Express leaves you entirely in charge of middleware, custom routes, and request handling. Next.js API routes don't offer as much freedom in this sense.
Real-Time Features: Do you need WebSockets or long-lived connections? Express plays nicely with things like Socket.io, whereas Next.js isn't exactly designed for that.
Scalability & Clean Architecture: Having your frontend (Next.js) separated from your backend (Express) keeps your project scalable and maintainable in the future.
What's the best way to do it? Utilize Next.js for SSR, authentication (NextAuth), and frontend logic. Utilize Express for database operations, file upload, and backend-intensive work. Have Next.js authenticate and pass the access token to your Express server to securely make API calls.