Initial commit
This commit is contained in:
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Ignore dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Ignore build output
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Ignore Git and VSCode configuration
|
||||||
|
.git
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Ignore tests
|
||||||
|
tests
|
||||||
|
|
||||||
|
# Ignore Docker files
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Ignore development-specific configuration
|
||||||
|
.prettierignore
|
||||||
|
cspell.json
|
||||||
|
eslint.config.ts
|
||||||
|
jest.config.js
|
||||||
|
pnpm-workspace.yaml
|
||||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
/dist
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local environment variables
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# OS-specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Test reports and coverage
|
||||||
|
/coverage
|
||||||
|
/jest-stare/
|
||||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.*
|
||||||
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": [
|
||||||
|
"source.fixAll.eslint"
|
||||||
|
],
|
||||||
|
"eslint.validate": ["javascript", "typescript"]
|
||||||
|
}
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 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" ]
|
||||||
308
README.md
Normal file
308
README.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# 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:** Fast, unopinionated, minimalist web framework for Node.js.
|
||||||
|
* **TypeScript:** Typed superset of JavaScript that compiles to plain JavaScript.
|
||||||
|
* **Jest:** A delightful JavaScript Testing Framework with a focus on simplicity.
|
||||||
|
* **Knip:** A tool to find unused files, dependencies, and exports in your JavaScript and TypeScript projects.
|
||||||
|
* **ESLint:** A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript.
|
||||||
|
* **Docker:** A platform for developing, shipping, and running applications in containers.
|
||||||
|
* **tsx:** A CLI to seamlessly execute TypeScript and ESM.
|
||||||
|
* **tsdown:** 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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# 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"
|
||||||
|
```
|
||||||
9
cspell.json
Normal file
9
cspell.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2",
|
||||||
|
"ignorePaths": ["src","dist", "node_modules", "coverage", "test", "tests", "package.json", "pnpm-lock.yaml", "pnpm-lock.yaml", "pnpm-lock.json", "pnpm-workspace.yaml", "cspell.json", "tsconfig.json", "tsconfig.build.json", "tsconfig.node.json", "tsconfig.test.json"],
|
||||||
|
"dictionaryDefinitions": [],
|
||||||
|
"dictionaries": [],
|
||||||
|
"words": [],
|
||||||
|
"ignoreWords": [],
|
||||||
|
"import": []
|
||||||
|
}
|
||||||
6
docker-compose.yml
Normal file
6
docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
exp-min:
|
||||||
|
build: .
|
||||||
|
container_name: "exp-min"
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
94
eslint.config.ts
Normal file
94
eslint.config.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
33
jest.config.js
Normal file
33
jest.config.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { createDefaultPreset } from "ts-jest";
|
||||||
|
|
||||||
|
const tsJestTransformCfg = createDefaultPreset().transform;
|
||||||
|
|
||||||
|
/** @type {import("jest").Config} **/
|
||||||
|
export const testEnvironment = "node";
|
||||||
|
export const transform = {
|
||||||
|
...tsJestTransformCfg,
|
||||||
|
'^.+\\.jsx?$': [
|
||||||
|
'ts-jest',
|
||||||
|
{
|
||||||
|
tsconfig: {
|
||||||
|
// Overrides the tsconfig.json module setting to allow for CommonJS modules in tests
|
||||||
|
allowJs: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
export const moduleNameMapper = {
|
||||||
|
// Handle module aliases
|
||||||
|
'^@routers/(.*)$': '<rootDir>/src/routers/$1',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const coverageThreshold = {
|
||||||
|
global: {
|
||||||
|
branches: 80,
|
||||||
|
functions: 80,
|
||||||
|
lines: 80,
|
||||||
|
statements: -10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const coverageReporters = ['text'];
|
||||||
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "exp-min",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./dist/index.mjs",
|
||||||
|
"dev": "tsx --watch ./src/index.ts",
|
||||||
|
"build": "tsdown",
|
||||||
|
"prestart": "pnpm run build",
|
||||||
|
"test": "jest --coverage",
|
||||||
|
"test:watch": "jest --coverage --watchAll",
|
||||||
|
"knip": "knip",
|
||||||
|
"lint": "eslint --fix ."
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "pnpm@10.12.1",
|
||||||
|
"dependencies": {
|
||||||
|
"consola": "^3.4.2",
|
||||||
|
"express": "^5.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.29.0",
|
||||||
|
"@stylistic/eslint-plugin": "^4.4.1",
|
||||||
|
"@types/express": "^5.0.3",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"@types/node": "^24.0.3",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
|
"eslint": "^9.29.0",
|
||||||
|
"eslint-plugin-n": "^17.20.0",
|
||||||
|
"jest": "^30.0.2",
|
||||||
|
"knip": "^5.61.2",
|
||||||
|
"supertest": "^7.1.1",
|
||||||
|
"ts-jest": "^29.4.0",
|
||||||
|
"tsdown": "^0.12.8",
|
||||||
|
"tsx": "^4.20.3",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"typescript-eslint": "^8.34.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
5428
pnpm-lock.yaml
generated
Normal file
5428
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ignoredBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- unrs-resolver
|
||||||
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
13
src/index.ts
Normal file
13
src/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { consola } from 'consola';
|
||||||
|
import server from './server';
|
||||||
|
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
const httpServer = server.listen(port, () => {
|
||||||
|
consola.info(`Example app listening on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
httpServer.on('error', (err: Error) => {
|
||||||
|
consola.error(err.message);
|
||||||
|
});
|
||||||
14
src/routers/firstRouter.ts
Normal file
14
src/routers/firstRouter.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Router, Request, Response } from 'express';
|
||||||
|
const firstRouter: Router = Router();
|
||||||
|
|
||||||
|
firstRouter.get('/', (req: Request, res: Response) => {
|
||||||
|
res.send('Hello from First Router root route');
|
||||||
|
});
|
||||||
|
|
||||||
|
firstRouter.get('/somepath/:param', (req: Request, res: Response) => {
|
||||||
|
res.send(
|
||||||
|
'Hello from First Router somePath route with param: ' + req.params.param,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default firstRouter;
|
||||||
14
src/routers/secondRouter.ts
Normal file
14
src/routers/secondRouter.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Router, Request, Response } from 'express';
|
||||||
|
const secondRouter: Router = Router();
|
||||||
|
|
||||||
|
secondRouter.get('/', (req: Request, res: Response) => {
|
||||||
|
res.send('Hello from Second Router root route');
|
||||||
|
});
|
||||||
|
|
||||||
|
secondRouter.get('/somepath/:param', (req: Request, res: Response) => {
|
||||||
|
res.send(
|
||||||
|
'Hello from Second Router somePath route with param: ' + req.params.param,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default secondRouter;
|
||||||
15
src/server.ts
Normal file
15
src/server.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import express, { Express, Request, Response } from 'express';
|
||||||
|
import firstRouter from '@routers/firstRouter';
|
||||||
|
import secondRouter from '@routers/secondRouter';
|
||||||
|
|
||||||
|
const app: Express = express();
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
app.use('/firstroute', firstRouter);
|
||||||
|
app.use('/secondroute', secondRouter);
|
||||||
|
|
||||||
|
app.get('/', (req: Request, res: Response) => {
|
||||||
|
res.send('Application is running!');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
15
tests/routers/firstRouter.test.ts
Normal file
15
tests/routers/firstRouter.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import request from 'supertest';
|
||||||
|
import server from '../../src/server';
|
||||||
|
|
||||||
|
it('should respond to /firstroute', async () => {
|
||||||
|
const res = await request(server).get('/firstroute');
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond to /firstroute/somepath/:para/', async () => {
|
||||||
|
const res = await request(server).get('/firstroute/somepath/myparam');
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
expect(res.text).toEqual(
|
||||||
|
'Hello from First Router somePath route with param: myparam',
|
||||||
|
);
|
||||||
|
});
|
||||||
15
tests/routers/secondRouter.test.ts
Normal file
15
tests/routers/secondRouter.test.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import request from 'supertest';
|
||||||
|
import server from '../../src/server';
|
||||||
|
|
||||||
|
it('should respond to /secondroute', async () => {
|
||||||
|
const res = await request(server).get('/secondroute');
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respond to /secondroute/somepath/:para/', async () => {
|
||||||
|
const res = await request(server).get('/secondroute/somepath/myparam');
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
expect(res.text).toEqual(
|
||||||
|
'Hello from Second Router somePath route with param: myparam',
|
||||||
|
);
|
||||||
|
});
|
||||||
10
tests/server.test.js
Normal file
10
tests/server.test.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import request from 'supertest'
|
||||||
|
import app from '../src/server'
|
||||||
|
|
||||||
|
describe('Express App', () => {
|
||||||
|
it('should respond with "Application is running!" at the root', async () => {
|
||||||
|
const res = await request(app).get('/')
|
||||||
|
expect(res.statusCode).toEqual(200)
|
||||||
|
expect(res.text).toBe('Application is running!')
|
||||||
|
})
|
||||||
|
})
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext", /* Specify what module code is generated. */
|
||||||
|
"moduleResolution": "Bundler", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
"baseUrl": "src", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
"paths": {
|
||||||
|
"@*": ["*"]
|
||||||
|
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tsdown.config.ts
Normal file
8
tsdown.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'tsdown';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: 'src/index.ts',
|
||||||
|
format: ['esm'],
|
||||||
|
target: 'ESNext',
|
||||||
|
platform: 'node',
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user