Documentation
This documentation covers the stack’s features, and workflows to help you build and run your ent-stack based project efficiently.
Express
Serves as the backend framework, managing routing, middleware, authentication, and database operations. Its flexibility supports seamless service integration and custom logic.
Next.js
Powers the frontend, offering server-side rendering, static generation, and a robust routing system. It simplifies building SEO-friendly, dynamic UIs with React’s reusable component model.
TRPC
Bridges frontend and backend with type-safe, end-to-end communication, eliminating schema duplication and reducing errors.
Authentication & Authorization
For authentication, a minimal passwordless flow is used:
- User inputs email.
- Frontend sends the email to the backend.
- Backend creates and stores verification PIN to the database and sends to the user’s inbox.
- User inputs PIN to the verification form.
- The backend validates the PIN against the database. On success, it generates an access token and a refresh token. The refresh token is stored in the database, and both tokens are set as cookies.
Subsequent requests include these tokens in cookies, allowing the backend to revalidate sessions and refresh tokens if needed.
Access Tokens
Access tokens are JWTs used to authenticate users with each request. These tokens are generated during login or registration, stored as HTTP-only cookies, and sent to the client to enable secure interactions with the backend.
- Stored in cookies to enhance security and prevent client-side manipulation.
- Used for authenticating API requests.
Refresh Tokens
Refresh tokens are UUIDs that allow users to renew their access tokens when they expire. The backend validates these tokens against the database before issuing a new access token, which is securely set as a server-only cookie in frontend middleware.ts
.
- Stored in the database for validation.
- Protects against access token expiration without requiring a full re-login.
- Frontend
middleware.ts
ensures secure handling of new access tokens.
Verification Tokens
Verification tokens are short PINs designed to confirm email ownership during login or registration. They are used instead of links because clicking a link in a mobile email client often opens a custom in-app browser, different from the user’s main browser, causing potential disruptions.
- Generated by the backend and stored in the database.
- Sent to the user’s email as a PIN.
- Users manually enter the PIN into a verification form on the website.
- Avoids complications with mobile email client behavior.
Handling invalid attempts
- Backend sends invalid login attempt when the user tries to login, even though he/she doesn’t have an account.
- Backend sends invalid registration attempt when the user tries to register, even though he/she already has an account.
Frontend Route Access Protection and Evaluation
- Route access can be set in
routes.ts
by setting route propertyprotected
totrue
- Access evaluation is done in
middleware.ts
- it checks if user is authenticated or not
Backend TRPC Route Access Protection and Evaluation
- Route access can be set by using public or protected Query or Mutation procedure method from
trpc.ts
- Access evaluation is done in
authenticationMiddleware
intrpc.ts
it checks if user is authenticated or not
Configuration
Feature configuration is in <section>-config.ts
classes and .env
files.
- These contain secrets like DB credentials, API keys, and environment-specific configuration.
- The T3 env packages are used for environment variable validation and type safety, both for backend and frontend.
TS configuration
TS configuration is in tsconfig.json
files.
- Main
tsconfig.json
file is in the root of the monorepo and it is extended by files inapps/backend
,apps/frontend
, andpackages/shared
.
Static code analysis configuration
Static code analysis configuration eslint.config.js
is the same for backend, frontend and shared code.
Database and ORM
The backend uses Drizzle ORM for interacting with a MySQL database, providing a type-safe and developer-friendly API.
The stack contains Docker image and commands for running the local database.
Configuration
Database connection settings are defined in the .env
file and utilized in drizzle.config.ts
.
Local Development
Use the command below to quickly apply schema changes during development.
Deployment Environments
For controlled updates in test, UAT, or production:
Management UI
Run pnpm db:studio
to access Drizzle Studio for a graphical interface to view and manage the database.
Mailing
The backend integrates Resend for delivering transactional emails.
Development and Testing
- Mailslurp is used for testing email functionality in development.
- A dedicated backend development-only email preview route
/email/:wildcard
enables rendering email templates directly in the browser for rapid testing.
Email Templates
Templates are built using Handlebars, allowing for dynamic content generation.
Implementation
Email functionality is managed through the following services:
email-service.ts
: Handles the core logic for sending emails.email-template-service.ts
: Manages the creation and rendering of dynamic email templates.
Error Handling
Error handling in this stack is designed to be robust, clean, and developer-friendly, leveraging modern tools and practices for both frontend and backend.
Frontend Error Handling
- User Feedback: Errors are handled with Sonner for toast notifications and inline field highlighting for invalid user inputs.
- Validation: Form inputs and user data are validated using Zod, ensuring clear, consistent validation rules across the app.
- Global Pages: Errors like 404 and server issues are managed with Next.js
global-error
andnot-found
pages, ensuring a seamless user experience.
Backend Error Handling
- Custom Error Classes: The backend uses custom error classes for granular error management and tRPC’s errorFormatter to standardize API responses.
- Development-Friendly Debugging: TRPC errorFormatter includes a
devOnly
object during development for detailed error insights. - Translated Error Messages: Logical validation errors and server issues are returned as localized, user-friendly messages, minimizing friction for end users.
- Uncaught Error Management: Express middleware serves as a final safety net, logging and gracefully handling uncaught errors.
Shared Error Handling Utilities
SharedErrorService and ErrorService are used as a practical abstraction around try/catch
. Methods of these classes replace repetitive blocks with a tuple-based approach:
- Success: Returns
[null, result]
- Failure: Returns
[error, null]
This makes error handling explicit, highlights error-prone sections, and keeps code clean by separating business logic from error management. It’s a lightweight, JavaScript-native approach that aligns with modern development practices, reducing noise and improving maintainability.
Internationalization (i18n) and Navigation
I18n is treated as a first-class citizen in this stack, providing comprehensive support for message translation, route translation, and navigation helpers.
Message Translation
Messages are handled by standalone functions in the shared package.
These functions utilize the int-messageformat
library to format messages and support the ICU Message Format.
Key features:
- They accept an optional
locale
parameter, defaulting to the application’s default locale if not provided. - These functions are accessible across the backend, frontend applications, and the shared package.
Route Translations
Using Next.js middleware, the app detects the locale from the URL and applies the appropriate translations.
- Translated routes are handled by
middleware.ts
using mappings fromroutes.ts
. - If you don’t need translations or locale prefixes, remove the route translation logic from
middleware
and adjust your app structure. - A locale prefix is required for all routes, except for the default locale.
- There is not built-in locale detection, it is up to the user to choose.
Navigation Helpers
Helpers in navigation.ts
generate localized URLs and are used in nav-link.tsx
and language-switcher.tsx
components.
Logging
Both the backend and frontend applications use Pino for logging, with log levels (e.g., info
, warn
, error
) configured through environment variables.
- Backend additionally uses pino-http for logging HTTP requests and responses.
In development, logs can be formatted for readability using pino-pretty. In production, logs are maintained in structured JSON format.
Styles
Tailwind is used for styling.
Here’s a revised version of your text with improved clarity and formatting, presented as Markdown code:
Testing
Playwright is used for testing both the backend and frontend.
Installation
To set up Playwright, run the following commands:
Test Commands
The test commands are defined in the top-level package.json
:
Backend Tests
Frontend Tests
Test Directories
Backend Tests: Located in apps/backend/tests
. Includes tests for API endpoints, TRPC functionality, and helper functions.
Frontend Tests: Located in apps/frontend/tests
. Includes end-to-end (E2E) tests, such as home.spec.ts
and menu.spec.ts
.
TRPC
TRPC (Type-safe Remote Procedure Call) is a library for building type-safe APIs in TypeScript.
trpc-client.ts
is used for server-to-server or SSR calls (no cookies sent by default).
trpc-client-react.ts
is used for browser-based usage, integrated with @tanstack/react-query
(query-client.ts
) for caching, optimistic updates, and robust async client-side state management.