The ENT Stack is a full-stack TypeScript project starter that integrates Express 5, Next.js 15, and TRPC.
It allows you to build and share code between frontend and backend in a single project while maintaining the flexibility to host them separately.
π PNPM Workspace - Monorepo
- The stack uses PNPM Workspace because it's simple and painless to use. With its efficient dependency management and shared package architecture, PNPM ensures faster installs and a clean, modular workflow.
- Within the monorepo, each top-level folder plays a distinct role:
- apps folder contains both the backend (
apps/backend
) and frontend (apps/frontend
) codebases. - packages folder contains a
shared
directory (packages/shared
) that centralizes common configurations, utilities, services type definitions, and translations.
- apps folder contains both the backend (
- To maintain uniform code quality, each of these directories includes an
eslint.config.js
file featuring consistent rules. A root-leveltsconfig.json
provides a base set of TypeScript configurations, while each subproject extends these defaults to meet its own requirements.
π€ Type-Safe API Layer
- The stack leverages TRPC to create a type-safe bridge between the frontend and backend. This ensures that your IDE automatically provides type hints and autocompletion for API endpoints, eliminating the need for manual type definitions.
βοΈ Environment and Configuration
- Leverages T3 Env type-safe environment variables validation in both backend and frontend
- Contains Shared configuration file
shared-config.ts
that is extended by both Backendbackend-config.ts
and Frontendfrontend-config.ts
files for feature-focused settings
π’οΈ Database
- The backend uses Drizzle ORM for interacting with a MySQL database, providing a type-safe and developer-friendly API.
- It also contains Docker Image and a script
bin/docker/run-local-db.sh
to run the database locally.
π Authentication and Authorization
- The stack provides passwordless registration and login through short verification PINs, secure JWT access tokens, and UUID-based refresh tokens. Additionally, frontend routes can be marked as protected in
routes.ts
, restricting them to authenticated users - this is evaluated inmiddleware.ts
.
πͺ² Validation and Error Handling
- The stack uses Zod for consistent input validation across the frontend and backend, integrated with TRPC for type-safe error management.
- Input is sanitized to prevent injection attacks.
- Errors are categorized into client, server, and userland types for precise handling.
- Tanstack Query handles errors through a custom handler with callbacks for server, client, and userland errors.
π State Management
- Uses Zustand for lightweight, synchronous global state management, delivering a minimal overhead approach for storing and controlling shared data across the application.
- Uses Tanstack Query for asynchronous data fetching and caching, providing an SSR-friendly solution that keeps the UI and server in sync.
π Internationalization (i18n) and Localization
- A custom, lightweight solution handles localization using standalone functions that use ICU message syntax.
- Translation functions can be used anywhere in the stack (both backend and frontend).
- The stack includes two scripts for message import (
pnpm import-messages
) and export (pnpm export-messages
) - Frontend routes are fully translatable, defined in standalone .ts file, and dynamically evaluated at runtime through Next.js middleware.
πͺ΅ Logging
- The stack employs Pino for lightweight and efficient logging across both backend and frontend. Its simple API and minimal overhead ensure clear, structured logs without impacting performance.
βοΈ Mailing
- integrates Resend for developer friendly e-mail sending.
- Backend also contains email test router for email previews and testing.
- Email templates are written in Handlebars.
π€ Testing
- The stack uses Playwright for unified frontend and backend testing, ensuring consistency and reliability.
- Mailslurp is used for receiving e-mails in tests.
π³ DevOPS - Infrastructure and CI/CD
- Example Infrastructure and CI/CD for the ENT Stack is in a separate repository.
Get up and running with the ENT Stack in just two steps:
Prerequisites
- Node.js
- PNPM
- Docker CLI (for local MySQL database)
- A Unix-like shell environment (e.g., Bash, Zsh)
π‘ How to install prerequisites on Ubuntu 24
pnpm create ent-stack@latest
- Creates a new project (folder) based on specific version of this repository.
- The version used is specified in the description of the npm package.
pnpm fire
- Installs dependencies
- Starts the local database in a Docker container
- Creates the database and tables
- Runs dev environments for both the backend and frontend
At this point, your application should be up and running locally. But to enable email sending and testing, you need to follow the final step below.
To enable mailing, you need to do the following:
- Sign up for a free Resend account and set SERVICE_EMAIL, SECURITY_EMAIL and RESEND_API_KEY in
apps/backend/.env
- Serves as a service for sending e-mails
![]() |
![]() |
- Sign up for a free MailSlurp account and set MAILSLURP_INBOX_ID and MAILSLURP_EMAIL in
apps/backend/.env
- Serves as a service for receiving e-mails (for testing purposes)
![]() |
![]() |
After that, make sure to restart dev environment, and then you can run the tests by executing the following commands:
- Test Backend
pnpm backend:test
- Test Frontend
pnpm frontend:test
Here is the list of values that MUST BE THE SAME in both backend and frontend .env files:
Backend | Frontend |
---|---|
SITE_NAME | NEXT_PUBLIC_SITE_NAME |
COOKIE_DOMAIN | NEXT_PUBLIC_COOKIE_DOMAIN |
BACKEND_URL | NEXT_PUBLIC_BACKEND_URL |
FRONTEND_URL | NEXT_PUBLIC_FRONTEND_URL |
JWT_SECRET | JWT_SECRET |
For information about ENT Stack features go to the π Documentation
1/ IDE issues
- Node interpreter is not set
- Eslint/Prettier is not working on save
- TypeScript highlights non-existent errors
- These issues can occur if your IDE is not properly configured. For reference, I'm attaching my PHPStorm (WebStorm) configuration on WSL2 with Ubuntu, showing the settings for ESLint, Prettier, and Node.js.:
![]() |
![]() |
![]() |
2/ MySQL container name conflict
docker: Error response from daemon: Conflict. The container name "/mysql" is already in use by container
- Remove the existing container by running
docker rm mysql
- Or for more nuclear solution, run
bin/docker/cleanup.sh
- this script forcefully cleans up Docker by removing all build caches, containers, images, and unused networks to free disk space and reset the environment.