309 lines
8.5 KiB
Markdown
309 lines
8.5 KiB
Markdown
# Minimal Express.js Scaffolding
|
||
|
||
This project offers a streamlined and minimalistic scaffolding for Express.js applications. Unlike many generators that require extensive cleanup before you can begin, this boilerplate strikes a balance between simplicity and completeness. It provides just the essentials, allowing you to pull the repository, customize the project name, and start building your API immediately.
|
||
|
||
## Support the Project
|
||
|
||
If you find this project useful, consider supporting its development:
|
||
|
||
[![Donate using Liberapay][liberapay-logo]][liberapay-link]
|
||
|
||
[liberapay-logo]: https://liberapay.com/assets/widgets/donate.svg "Liberapay Logo"
|
||
[liberapay-link]: https://liberapay.com/sfiorini/donate
|
||
|
||
## Installation
|
||
|
||
To install the project dependencies, run the following command:
|
||
|
||
```bash
|
||
pnpm install
|
||
```
|
||
|
||
## Running the App
|
||
|
||
To run the application in development mode with live reloading, use:
|
||
|
||
```bash
|
||
pnpm dev
|
||
```
|
||
|
||
To build and run the application for production, use:
|
||
|
||
```bash
|
||
pnpm start
|
||
```
|
||
|
||
## Customization
|
||
|
||
To rename the project, modify the `"name"` field in the `package.json` file.
|
||
|
||
```json
|
||
{
|
||
"name": "your-project-name"
|
||
}
|
||
```
|
||
|
||
## Technologies Used
|
||
|
||
* **[Express](https://expressjs.com/):** Fast, unopinionated, minimalist web framework for Node.js.
|
||
* **[TypeScript](https://www.typescriptlang.org/):** Typed superset of JavaScript that compiles to plain JavaScript.
|
||
* **[Jest](https://jestjs.io/):** A delightful JavaScript Testing Framework with a focus on simplicity.
|
||
* **[Knip](https://knip.dev/):** A tool to find unused files, dependencies, and exports in your JavaScript and TypeScript projects.
|
||
* **[ESLint](https://eslint.org/):** A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript.
|
||
* **[Docker](https://www.docker.com/):** A platform for developing, shipping, and running applications in containers.
|
||
* **[tsx](https://tsx.is/):** A CLI to seamlessly execute TypeScript and ESM.
|
||
* **[tsdown](https://tsdown.dev/):** A tool for building TypeScript projects.
|
||
|
||
## Scripts
|
||
|
||
* `pnpm start`: Starts the production server after building the project.
|
||
* `pnpm dev`: Runs the application in development mode with live reloading.
|
||
* `pnpm build`: Builds the TypeScript project into JavaScript.
|
||
* `pnpm test`: Runs the test suite using Jest and generates a coverage report.
|
||
* `pnpm test:watch`: Runs the tests in watch mode, re-running them on file changes.
|
||
* `pnpm knip`: Finds unused files, dependencies, and exports.
|
||
* `pnpm lint`: Lints the codebase using ESLint and automatically fixes issues.
|
||
|
||
## Building This Scaffold From Scratch
|
||
|
||
Here is a detailed, step-by-step guide to creating this scaffold from the ground up.
|
||
|
||
### Requirements
|
||
|
||
* [Node.js](https://nodejs.org/)
|
||
* [pnpm](https://pnpm.io/)
|
||
|
||
### Step-by-Step Guide
|
||
|
||
1. **Initialize the project:**
|
||
|
||
```bash
|
||
pnpm init
|
||
```
|
||
|
||
2. **Install Express:**
|
||
|
||
```bash
|
||
pnpm install express
|
||
```
|
||
|
||
3. **Set up TypeScript:**
|
||
|
||
```bash
|
||
pnpm install --save-dev typescript @types/node @types/express
|
||
npx tsc --init
|
||
```
|
||
|
||
4. **Create the main application file:**
|
||
Create a file at `src/index.ts`.
|
||
|
||
5. **Install `tsx` for development and `tsdown` for building:**
|
||
|
||
```bash
|
||
pnpm install --save-dev tsx tsdown
|
||
```
|
||
|
||
6. **Configure `tsdown`:**
|
||
Create a `tsdown.config.ts` file:
|
||
|
||
```typescript
|
||
import { defineConfig } from 'tsdown';
|
||
|
||
export default defineConfig({
|
||
entry: 'src/index.ts',
|
||
format: ["esm"],
|
||
target: "ESNext",
|
||
platform: "node"
|
||
});
|
||
```
|
||
|
||
7. **Configure `tsconfig.json`:**
|
||
Modify your `tsconfig.json` to look like this:
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "ESNext",
|
||
"module": "ESNext",
|
||
"moduleResolution": "Bundler",
|
||
"baseUrl": "src",
|
||
"paths": {
|
||
"@*": ["*"]
|
||
},
|
||
"esModuleInterop": true,
|
||
"forceConsistentCasingInFileNames": true,
|
||
"strict": true,
|
||
"skipLibCheck": true
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
8. **Set up Jest for testing:**
|
||
|
||
```bash
|
||
pnpm install --save-dev jest ts-jest @types/jest @types/supertest supertest
|
||
npx ts-jest config:init
|
||
```
|
||
|
||
This will create a `jest.config.js` file.
|
||
|
||
9. **Refactor the application for testability:**
|
||
* Move all your Express app definition logic from `src/index.ts` into a new `src/server.ts` file.
|
||
* The `src/index.ts` file should only contain the `server.listen()` part, which starts the server.
|
||
* Create a `tests` folder and mirror the structure of your `src` folder for your test files.
|
||
|
||
10. **Install Knip to keep the project clean:**
|
||
|
||
```bash
|
||
pnpm add -D knip
|
||
```
|
||
|
||
11. **Set up ESLint for code linting:**
|
||
|
||
```bash
|
||
pnpm add --save-dev eslint jiti @eslint/js typescript-eslint @stylistic/eslint-plugin eslint-plugin-n
|
||
```
|
||
|
||
Create an `eslint.config.ts` file with the following settings (modify as needed):
|
||
|
||
```typescript
|
||
import eslint from '@eslint/js';
|
||
import tseslint from 'typescript-eslint';
|
||
import stylistic from '@stylistic/eslint-plugin';
|
||
import nodePlugin from 'eslint-plugin-n';
|
||
|
||
export default tseslint.config(
|
||
eslint.configs.recommended,
|
||
nodePlugin.configs['flat/recommended-script'],
|
||
...tseslint.configs.strictTypeChecked,
|
||
...tseslint.configs.stylisticTypeChecked,
|
||
{
|
||
ignores: [
|
||
'**/node_modules/*',
|
||
'**/*.mjs',
|
||
'**/*.js',
|
||
],
|
||
},
|
||
{
|
||
languageOptions: {
|
||
parserOptions: {
|
||
project: './tsconfig.json',
|
||
warnOnUnsupportedTypeScriptVersion: false,
|
||
},
|
||
},
|
||
},
|
||
{
|
||
plugins: {
|
||
'@stylistic/js': stylistic,
|
||
'@stylistic/ts': stylistic,
|
||
},
|
||
},
|
||
{
|
||
files: ['**/*.ts'],
|
||
},
|
||
{
|
||
rules: {
|
||
'@typescript-eslint/explicit-member-accessibility': 'warn',
|
||
'@typescript-eslint/no-misused-promises': 0,
|
||
'@typescript-eslint/no-floating-promises': 0,
|
||
'@typescript-eslint/no-confusing-void-expression': 0,
|
||
'@typescript-eslint/no-unnecessary-condition': 0,
|
||
'@typescript-eslint/restrict-template-expressions': [
|
||
'error', { allowNumber: true },
|
||
],
|
||
'@typescript-eslint/restrict-plus-operands': [
|
||
'warn', { allowNumberAndString: true },
|
||
],
|
||
'@typescript-eslint/no-unused-vars': 'warn',
|
||
'@typescript-eslint/no-unsafe-enum-comparison': 0,
|
||
'@typescript-eslint/no-unnecessary-type-parameters': 0,
|
||
'@stylistic/js/no-extra-semi': 'warn',
|
||
'max-len': [
|
||
'warn',
|
||
{
|
||
'code': 80,
|
||
},
|
||
],
|
||
'@stylistic/ts/semi': ['warn', 'always'],
|
||
'@stylistic/ts/member-delimiter-style': ['warn', {
|
||
'multiline': {
|
||
'delimiter': 'comma',
|
||
'requireLast': true,
|
||
},
|
||
'singleline': {
|
||
'delimiter': 'comma',
|
||
'requireLast': false,
|
||
},
|
||
'overrides': {
|
||
'interface': {
|
||
'singleline': {
|
||
'delimiter': 'semi',
|
||
'requireLast': false,
|
||
},
|
||
'multiline': {
|
||
'delimiter': 'semi',
|
||
'requireLast': true,
|
||
},
|
||
},
|
||
},
|
||
}],
|
||
'@typescript-eslint/no-non-null-assertion': 0,
|
||
'@typescript-eslint/no-unused-expressions': 'warn',
|
||
'comma-dangle': ['warn', 'always-multiline'],
|
||
'no-console': 1,
|
||
'no-extra-boolean-cast': 0,
|
||
'indent': ['warn', 2],
|
||
'quotes': ['warn', 'single'],
|
||
'n/no-process-env': 1,
|
||
'n/no-missing-import': 0,
|
||
'n/no-unpublished-import': 0,
|
||
'prefer-const': 'warn',
|
||
},
|
||
},
|
||
);
|
||
```
|
||
|
||
12. **Set up Docker:**
|
||
Create a `Dockerfile` file:
|
||
|
||
```dockerfile
|
||
# Use the official Node.js image.
|
||
FROM node:22-slim
|
||
|
||
# Create and change to the app directory.
|
||
WORKDIR /usr/src/app
|
||
|
||
# Copy package.json and pnpm-lock.yaml
|
||
COPY package.json pnpm-lock.yaml ./
|
||
|
||
# Install pnpm
|
||
RUN npm install -g pnpm
|
||
|
||
# Install dependencies
|
||
RUN pnpm install
|
||
|
||
# Copy the rest of the application's source code.
|
||
COPY . .
|
||
|
||
# Build the project
|
||
RUN pnpm run build
|
||
|
||
# Expose the port the app runs on
|
||
EXPOSE 3000
|
||
|
||
# Serve the app
|
||
CMD [ "pnpm", "start" ]
|
||
```
|
||
|
||
Create a `docker-compose.yml` file:
|
||
|
||
```yaml
|
||
services:
|
||
exp-min:
|
||
build: .
|
||
container_name: "exp-min"
|
||
ports:
|
||
- "3000:3000"
|
||
```
|