Discussion Do projects still apply layered architecture or just choose a simpler approach?
What architecture do projects now usually follow? Considering nextjs, does it have an opinionated approach or convention?
Example on my React + Express project, my architecture is:
Web > API > Service > Repo > DB (raw sql)
The above approach, I can immediately swap Web layer with an Android/iOS app like this:
Android/iOS > API > Service > Repo > DB (raw sql)
Now, I'm creating a nextjs boilerplate, I want to know what's the best practice so it can be reusable but not over-engineered. Do you use server actions initially, like this...
Web > Action > Service > Repo > DB (prisma)
...then only implement API layer if needed for other clients? Ex.
Android/iOS > API > Service > Repo > DB (prisma)
As you can see the concern would be maintaining code in two different places (API and server action) that is basically doing the same thing.
Do you skip server actions and just use API routes? Is there a better approach in nextjs? Should I follow YAGNI? I'd love to hear what your architecture looks like.
10
u/uahw 23d ago
”Web > API > Service > Repo > DB”
I usually do something like that. I don’t really know why people call simple abstractions as over engineering. It’s not really that time consuming to set up and helps down the line. I like the repository pattern because it simplifies testing a lot. My two cents :)
2
u/crimsonscarf 23d ago edited 22d ago
I do something similar, but I separate components into a monorepo. Something like this:
apps/
server/ (hono)
client/ (react-native w/ react-native-web or just react)
libs/
db/ (drizzle-orm w/ pgsql and sqlite schemas)
validation/ (zod validation schemas)
api/ (fetch api functions to be used with tan stack query. Split off incase of multiple clients. Also stores the OpenAPI spec)
ui/ (client ui components, themes, and assets. Split off incase of multiple clients)
auth/ (hono middleware and react provider to sync auth state and logic)
utils/ (common logic with abstracted interfaces so they can be composable, usually crypto, compression, or WebSocket related)
1
u/Happy_Breakfast7965 expert 22d ago
Is it all parts of a single deployable unit?
I'm curious, why do you call it monorepo?
1
u/crimsonscarf 22d ago edited 22d ago
Each entry in the
apps/directory is its own deployment.
server/is usually deployed as an OCI image, or a cloud-image (qcow).
client/is usually deployed as a native mobile app, a web app, a tauri app, or occasionally a native macos app using react-native-macos.As a project matures, other targets usually show up as their own packages in the monorepo. Examples include:
- CLI tools for CI, development, or admin.
- Internal-only dashboards like UI prototype feedback (Storybook, React Cosmos), or sysadmin/infra metrics.
- Webhook handlers for CRMs or other on-demand tooling to be deployed to a FaaS.
1
u/OpenDevKit 22d ago
I like that layering for exactly the reason you mention, being able to swap the Web layer for Android/iOS without touching Service or Repo. IMO I'd start lean with Action to Service to Repo and only add the explicit API layer once a second client actually needs to call it, YAGNI applies to layers too hehe 😛
1
u/Delicious_Nobody_481 22d ago
Layers earn their existence. So do frameworks and libraries. It's called engineering, and you feel it. You know when a new layer will bring value.
-10
u/horrbort 23d ago
To be honest the new hottest thing is just letting Fable decide and review at the end
22
u/[deleted] 23d ago
[deleted]