A simple template for a ready-to-build, full-stack application written completely in Typescript using:
- Vite and React 18 for building a single-page application (SPA);
- Nitro for defining the API layer and serving the full application (SPA + API);
- Vitest for testing;
- Biome for code standards and formatting;
- Lefthook for Git hooks.
The template is not opinionated about tooling for:
- Authentication and authorization (e.g. Authkit, Auth0, Supertokens)
- CI/CD pipelines (e.g. GitHub Actions, CircleCI)
- Client-side routing (e.g. React Router, TanStack Router)
- Component/E2E/other types of testing (e.g. Storybook, Cypress, Playwright)
- Data and state management (e.g. Redux, Redux Toolkit Query, TanStack Query)
- Forms management (e.g. React Hook Form, Formik)
- Internationalization (e.g. react-i18next, typesafe-i18n)
- Styling (e.g. styled components, Tailwind)
- UI/components (e.g. Radix, Mantine, Chakra UI)
- Validation (e.g. zod, Yup)
Start coding in minutes, then scale at your pace - based on what you need, what you like, and how your application evolves.
The application can be deployed anywhere where Nitro can be deployed.
- Node >=v22
- npm (or your preferred package manager) and base knowledge about its commands/behavior
All commands below assume npm as package manager, for simplicity. If you use a different manager - be aware that certain tools like Lefthook may have additional specifications for other managers like pnpm, so take a quick look at the documentation to see if you need to make adjustments. You'll also want to do a text search for "npm" in the repository to make sure you replace all usages.
npm start # start the application in development mode
npm test # run tests
npm run check # run linting and formatting checks on all files
npm run fmt # apply linting and formatting to all files
npm run typecheck # run typecheck
npm run prepare # ensure local setup is ready
npm run build # bundle the application for production
npm run preview # serve the output of `npm run build`
The Javascript ecosystem is always evolving, and it's hard to keep up while avoiding compatibility issues, vendor lock-in, and constantly learning new abstractions. This template uses a small, modern, performant set of tools that play well with each other, without making any part irreplaceable. The main goals are modularity, simplicity, and avoiding unseen "magic" - while promoting code quality and a great development experience from the start.
The sections below introduce each tool, and how to remove/replace it based on your own preferences.
At the time of writing, the project has no known vulnerable dependencies.
Handles linting, formatting, and code quality checks. This is an alternative to tools like ESLint and Prettier. Biome is extremely fast, supports Typescript out-of-the-box, provides great CLI feedback, and intentionally keeps things simple - avoiding extensive configuration files and complex plugin systems that take time to maintain, upgrade, and ensure compatibility for.
The template mainly uses Biome's defaults, with only a handful of configurations (see the biome.json
file).
If you use an IDE, you may want to setup its integration with Biome if available (for example, to use format-as-you-type or format-on-save features). See available integrations here.
To remove Biome:
- Remove it from the project dependencies:
npm uninstall @biomejs/biome
. - Delete the configuration file,
biome.json
. - Update (or remove) the
"check"
and"fmt"
commands frompackage.json
. - Update the Lefthook configuration file (
lefthook.yaml
) accordingly:- If you deleted the commands and do not want to use a linter/formatter at all: remove the
pre-push
hook. - If you updated the commands to use your own preferred tools: update the
pre-push
hook as needed. Read the Lefthook section below for more details.
- If you deleted the commands and do not want to use a linter/formatter at all: remove the
Provides convenient automations while developing, based on Git hooks. This is an alternative to tools like Husky and lint-staged. Lefthook is easy to configure, works with any stack, and enables direct reuse of commands from package.json
so that all configuration is centralized. It's directly installable and maintainable as a single project dependency, rather than requiring additional installation steps.
The template configures two Git hooks:
post-checkout
: on branch checkout, it installs the project's dependencies withnpm install
. In turn, this will run the"prepare"
script frompackage.json
(npm documentation), which ensures Lefthook is configured and also refreshes your local Nitro artefacts.pre-push
: on commit push, it runs the"fmt"
script frompackage.json
. This is an intentional departure from the classic "pre-commit" setup, to enable fast iteration locally (without worrying about writing fully clean, conformant code) and defer formatting/validating code until it's ready to be pushed. The command only runs on the actual files being pushed, for optimal speed.
To remove Lefthook:
- Uninstall it from the project dependencies:
npm uninstall lefthook
. - Delete the configuration file,
lefthook.yaml
. - Update the
"prepare"
command frompackage.json
to remove"lefthook install"
.
Creates a web server for Javascript runtimes. This is an alternative to tools like Express, Hono, and Fastify. Nitro provides an intuitive API for defining routes, aligned with web standards and promoting modularity & composability, with full Typescript support. It's easy to extend with custom or library code, with compatibility adapters for multiple middleware types, and offers hot module replacement (HMR) during local development.
The template uses a minimal Nitro configuration (see nitro.config.ts
) to expose:
- An index route (
index.ts
), which serves the client app as a single-page application (SPA). - An API endpoint (
api/health.ts
) that returns a healthcheck.
In development mode, the server integrates with Vite through a custom plugin and middleware - this provides a way to start the full application locally from a single command, with hot module replacement (HMR) for both client and server logic.
In production mode, the server expects to find the app's static assets in the dist
directory and will serve them directly.s
Auto-imports and virtual imports are disabled to prevent "magic" behavior. If you're familiar with how this works and know what you're doing, you can restore those functionalities:
- Auto-import
- Remove the
imports.autoImports
parameter innitro.config.ts
, so that it uses the default value (true
). - Add
.nitro/types/nitro-imports.d.ts
to theinclude
list intsconfig.server.json
.
- Remove the
- Virtual imports
- Remove the
typescript.generateTsConfig
parameter innitro.config.ts
, so that it uses the default value (true
). - Update
tsconfig.server.json
so that itextends
from./nitro/types/tsconfig/json
(order is important):"extends": ["./nitro/types/tsconfig/json", "./tsconfig.json"]
. - Remove the
.nitro/*
files from theinclude
list.
- Remove the
If you do so, be aware that additional changes will be needed if you want to import from Nitro files when writing tests, because the test runner will need to be able to find the different imports.
To replace Nitro:
- Uninstall it from the project's dependencies:
npm uninstall nitropack
. - Delete the configuration file,
nitro.config.ts
. - Update
.gitignore
to remove the entries under "Nitro". - Remove all type references:
- Update
tsconfig.server.json
andtsconfig.app.json
to remove all.nitro/*
files from theinclude
list. - Delete
src/types.ts
.
- Update
- Migrate the relevant
package.json
commands ("build"
,"prepare"
,"preview"
,"start"
) and the code from theserver
directory. The exact steps will depend on what you want to replace Nitro with. Read the documentation for each of the commands and the server README to understand what you need to re-implement.
To replace React:
- Uninstall the related project dependencies:
npm uninstall react react-dom @vitejs/plugin-react @types/react @types/react-dom
. - Remove the plugin from Vite's configuration file (
vite.config.ts
). - Update
src/main.tsx
andsrc/app/index.tsx
to render app code with your chosen framework (e.g. Vue, Svelte, Preact).
You may also need to update the Typescript configuration for client files (tsconfig.app.json
).
The template uses strict typechecking, with a few additional rules to improve code quality (see tsconfig.json
). Two projects are used to define the appropriate runtimes for different parts of the app:
tsconfig.app.json
for client files. This also makes the server's API schema available for use in client code via the auto-generated.nitro/types/nitro-routes.d.ts
.tsconfig.server.json
for server files. This also makes available the models for the server's runtime configuration (.nitro/types/nitro-config.d.ts
) and routes (.nitro/types/nitro-routes.d.ts
) for improved typechecking.- This is a necessary departure from what the Nitro documentation recommends, because the template disables auto-imports and virtual imports (see the Nitro section for more context).
Manages the bundling of client files into static assets. This is an alternative to tools like Parcel, Rsbuild, and Webpack. Vite provides a performant dev server with hot module replacement (HMR), interfaces for composability (e.g. for SSR and backend integrations), and a lean core that is easy to extend.
The template uses default configuration for Vite, with the React plugin (@vitejs/plugin-react
). See vite.config.ts
.
Testing framework. This is an alternative to tools like Jest, Mocha, and Jasmine. Vitest can directly integrate with Vite, allowing to consolidate bundling configuration into one file (vite.config.ts
), and provides similar performance benefits as Vite for local development.
The template uses default configuration for Vitest (no vitest.config.ts
).
To remove Vitest:
- Uninstall it from the project's dependencies:
npm uninstall vitest
. - Update or remove the
"test"
command frompackage.json
. - Update or remove the
server/tests
code as needed.