Full Stack Web App Built with Next.js App Router and PostgreSQL

Jul 3, 2025

software-development

Since my experience in front-end development is mostly with React, I recently built a full-stack movie app using the Next.js App Router and PostgreSQL to get more comfortable with these technologies. The app supports searching, adding, editing, and deleting movies, as well as authentication. It’s open source.

This post is a companion to the source code and gives an overview of the project structure along with explanations of files and directories using a tree UI component.

It’s assumed that you have a basic familiarity with the Next.js App Router.

movie-app project structure:

+
app
This is the Next.js app directory. The Next.js app directory is essentially a routing system that uses folders and special files (e.g. page.tsx) to define routes. It also supports layouts which is basically a UI that is shared between multiple pages.
+
components
Inside this folder, components using client-specific features (e.g. state, effects) go in the client folder. Those using server-specific features (e.g. async components) go in the server folder. Components that use neither can technically work in both contexts (for example if they're imported by a client component, they'll automatically become part of the client bundle) - so they are placed outside of these folders.
+
hooks
Custom React hooks.
+
lib
Folder for some server-side logic.
+
server-functions
Server functions allow client components to call asynchronous functions that are executed on the server. When that function is called on the client, React will send a request to the server to execute the function, and return the result. They are used for updating data.
middleware.ts
Middleware for protecting routes based on authentication.
utils.ts
Shared utilities for server and client.

Notes

Redirect from middleware not working properly with server functions

During working on this project I encountered one problem. Currently even Nextjs docs mention that you can perform auth checks inside middleware (though they mention this should not be your only line of defense). So you often encounter code like this inside middleware:

if (isProtectedRoute && !session) {
  return NextResponse.redirect(new URL('/login', req.nextUrl))
}

This works most of the time but there was one situation where it was causing a problem.

Suppose we are on a protected page and the session expires. If then I invoked a server function from that page:

startTransition(async () => {
  // Call a server function
  const result = await addMovie(data);
  // ...
});

the addMovie call triggered the middleware. Because we are on a protected page and the session had expired, the middleware attempted to redirect using NextResponse.redirect (as shown in the previous if check).

However apparently since the middleware was called due to server function, the redirect didn't happen. The addMovie function itself never ran and simply returned undefined.

My current solution is from here, to configure the middleware such that it ignores server functions. I do auth checks in server functions though.

Source Code

You can find the source code here.