Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@ name: Deploy Docs

on:
push:
branches: [main]
branches:
- main
- next
- "v*"
paths:
- 'docs/**'
- '.github/workflows/docs.yml'

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false
contents: write

jobs:
build:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -38,21 +35,32 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Determine Base Path
id: vars
run: |
BRANCH=${GITHUB_REF#refs/heads/}
if [ "$BRANCH" = "main" ]; then
echo "BASE=/express-cli/" >> $GITHUB_OUTPUT
echo "FOLDER=." >> $GITHUB_OUTPUT
elif [ "$BRANCH" = "next" ]; then
echo "BASE=/express-cli/next/" >> $GITHUB_OUTPUT
echo "FOLDER=next" >> $GITHUB_OUTPUT
elif [[ $BRANCH == v* ]]; then
# Extract major version, e.g., v1 from v1.x
VERSION=$(echo $BRANCH | cut -d'.' -f1)
echo "BASE=/express-cli/$VERSION/" >> $GITHUB_OUTPUT
echo "FOLDER=$VERSION" >> $GITHUB_OUTPUT
fi

- name: Build with VitePress
run: pnpm run docs:build
env:
VITEPRESS_BASE: ${{ steps.vars.outputs.BASE }}

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist
destination_dir: ${{ steps.vars.outputs.FOLDER }}
keep_files: true # CRITICAL: This keeps other versions alive!
20 changes: 17 additions & 3 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
scope: '@repo'

- name: Setup pnpm
uses: pnpm/action-setup@v3
Expand All @@ -55,9 +57,20 @@ jobs:

- name: Version and Publish
run: |
BRANCH=${GITHUB_REF#refs/heads/}
# Detect branch name correctly for both Push and PR
if [ "${{ github.event_name }}" = "pull_request" ]; then
BRANCH=${{ github.head_ref }}
else
BRANCH=${GITHUB_REF#refs/heads/}
fi
echo "🌿 Current branch: $BRANCH"

# 🛑 SKIP VERSIONING/PUBLISHING ON PULL REQUESTS OR DEV BRANCH
if [ "${{ github.event_name }}" = "pull_request" ] || [ "$BRANCH" = "dev" ]; then
echo "🔍 Pull Request or DEV branch detected. Skipping versioning and publishing logic."
exit 0
fi

# Setup git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
Expand Down Expand Up @@ -99,11 +112,12 @@ jobs:
# Add extra tags if defined
if [ -n "$EXTRA_TAG" ]; then
echo "🏷️ Adding extra tag: $EXTRA_TAG"
# Loop through all packages and add the extra tag to the newly published version
pnpm -r exec sh -c 'npm dist-tag add $(node -p "require(\"./package.json\").name")@$(node -p "require(\"./package.json\").version") '$EXTRA_TAG || echo "⚠️ Could not add extra tag"
# Loop through all packages and add the extra tag to the newly published version (skipping private packages)
pnpm -r exec sh -c 'if [ "$(node -p "require(\"./package.json\").private")" != "true" ]; then npm dist-tag add $(node -p "require(\"./package.json\").name")@$(node -p "require(\"./package.json\").version") '$EXTRA_TAG'; fi' || echo "⚠️ Could not add extra tag"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


14 changes: 14 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pnpm run check-types
pnpm run lint
pnpm run build
pnpm run test

# 🚀 Mandatory Changeset Check
# If files in packages/ are changed, a changeset MUST be included.
if git diff --cached --name-only | grep -q "^packages/"; then
if ! git diff --cached --name-only | grep -q "^\.changeset/.*\.md$"; then
echo "❌ ERROR: You have modified packages but no changeset was found."
echo "💡 Please run 'pnpm changeset' to add one before committing."
exit 1
fi
fi
21 changes: 16 additions & 5 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { defineConfig } from "vitepress";

// Detect current version/branch from environment variable for multi-version support
const base = process.env.VITEPRESS_BASE || "/express-cli/";

export default defineConfig({
title: "Express Forge",
description: "⚡ Production-ready Express backends in seconds",
base: base,

// Important for GitHub Pages deployment
base: "/express-cli/",

head: [["link", { rel: "icon", href: "/express-cli/logo.svg" }]],
head: [["link", { rel: "icon", href: `${base}logo.svg` }]],

themeConfig: {
logo: "/logo.svg",
Expand All @@ -19,6 +20,14 @@ export default defineConfig({
nav: [
{ text: "Guide", link: "/guide/getting-started" },
{ text: "Features", link: "/guide/features" },
{
text: "Versions",
items: [
{ text: "Latest (v2.x)", link: "https://code-y02.github.io/express-cli/" },
{ text: "Beta (Next)", link: "https://code-y02.github.io/express-cli/next/" },
{ text: "Legacy (v1.x)", link: "https://code-y02.github.io/express-cli/v1/" },
],
},
{ text: "Reference", link: "/reference/cli-options" },
],

Expand All @@ -28,7 +37,6 @@ export default defineConfig({
items: [
{ text: "What is Express Forge?", link: "/" },
{ text: "Getting Started", link: "/guide/getting-started" },
{ text: "Roadmap", link: "/roadmap" },
],
},
{
Expand All @@ -37,6 +45,9 @@ export default defineConfig({
{ text: "Architecture Patterns", link: "/guide/architecture" },
{ text: "Project Structure", link: "/guide/structure" },
{ text: "Core Features", link: "/guide/features" },
{ text: "Authentication", link: "/guide/auth" },
{ text: "Caching", link: "/guide/caching" },
{ text: "API Documentation", link: "/guide/openapi" },
],
},
{
Expand Down
8 changes: 7 additions & 1 deletion docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,14 @@ h1, h2, h3, h4, h5, h6 {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

/* Hero Buttons */
.VPHero .actions {
justify-content: center !important;
gap: 12px !important;
}

/* Overflow & Clipping Prevention */
* {
.vp-doc p, .vp-doc li {
overflow-wrap: break-word;
}

Expand Down
43 changes: 43 additions & 0 deletions docs/guide/auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Authentication

Express Forge provides two battle-tested authentication strategies: **JWT** (JSON Web Tokens) and **Sessions**.

## Strategies

### 🔐 JWT (Stateless)
The modern standard for web APIs. Highly scalable and perfect for mobile apps and SPAs.
- **Middleware**: `src/middlewares/auth.middleware.ts`
- **Config**: Set `JWT_SECRET` in your `.env`.

### 🍪 Session (Stateful)
Traditional cookie-based authentication. Excellent for server-side rendered apps or when you need built-in session management.
- **Middleware**: `src/middlewares/auth.middleware.ts`
- **Config**: Set `SESSION_SECRET` in your `.env`.

## Using the Auth Middleware

To protect a route, simply add the `auth` middleware to your route definition.

```typescript
import { auth } from '../middlewares/auth.middleware.js';
import { userController } from '../controllers/user.controller.js';

// Protected route
router.get('/profile', auth, userController.getProfile);
```

## Accessing the User

Once a user is authenticated, their information is attached to the `req.user` object (for JWT) or `req.session.user` (for Sessions).

```typescript
export const getProfile = (req: Request, res: Response) => {
const user = req.user; // For JWT
return ApiResponse.success(res, user);
};
```

## Security Best Practices
1. **Secret Management**: Never commit your `JWT_SECRET` or `SESSION_SECRET` to version control. Use `.env` files.
2. **HTTPS**: Always serve your API over HTTPS in production to protect tokens and session cookies.
3. **HTTP-Only Cookies**: If using sessions, Express Forge pre-configures cookies to be `httpOnly` to prevent XSS attacks.
50 changes: 50 additions & 0 deletions docs/guide/caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Caching

Express Forge provides a flexible caching layer to boost your API performance and reduce database load. You can choose between **Redis** (distributed) or **Node-Cache** (in-memory) during the scaffolding process.

## Supported Drivers

### 🔴 Redis
Recommended for production environments and distributed systems where multiple server instances need to share a cache.
- **Requirement**: A running Redis instance.
- **Config**: Set `REDIS_URL` in your `.env` file.

### 💾 Node-Cache
An in-memory caching solution that requires zero external dependencies. Perfect for simple applications or single-server setups.
- **Requirement**: None.
- **Config**: Automatic.

## Usage

The caching logic is encapsulated in `src/cache/index.ts`. It provides a unified interface regardless of the driver you chose.

### Getting a Value
```typescript
import { cache } from '../cache/index.js';

const user = await cache.get('user:123');
if (user) {
return JSON.parse(user);
}
```

### Setting a Value
You can optionally set a Time-To-Live (TTL) in seconds.
```typescript
import { cache } from '../cache/index.js';

// Cache for 1 hour (3600 seconds)
await cache.set('user:123', JSON.stringify(userData), 3600);
```

### Deleting a Value
```typescript
import { cache } from '../cache/index.js';

await cache.del('user:123');
```

## Best Practices
1. **Cache Invalidation**: Always delete or update the cache when the underlying data in the database changes.
2. **Serialization**: Since Redis only stores strings, ensure you `JSON.stringify()` your objects before setting and `JSON.parse()` when getting.
3. **Fail-Safe**: Express Forge handles Redis connection errors gracefully via the logger, preventing your entire app from crashing if the cache is down.
43 changes: 43 additions & 0 deletions docs/guide/openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# API Documentation (OpenAPI)

Express Forge integrates **Swagger UI** to provide interactive, live documentation for your API. This allows your frontend team or external partners to test endpoints directly from the browser.

## Getting Started

If you enabled OpenAPI during scaffolding, your documentation is available at:
`http://localhost:3000/docs`

## Configuration

The documentation configuration is located in `src/docs/swagger.ts`. It uses `swagger-jsdoc` to parse JSDoc comments in your route files.

## Documenting Endpoints

To add an endpoint to the Swagger UI, simply add a `@openapi` or `@swagger` block above your route definition.

### Example

```typescript
/**
* @openapi
* /users:
* get:
* summary: Retrieve a list of users
* description: Returns a list of users from the database.
* responses:
* 200:
* description: A list of users.
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
router.get('/', userController.getAllUsers);
```

## Benefits
- **Live Testing**: Use the "Try it out" button to make real requests to your development server.
- **Auto-Sync**: Your documentation lives next to your code, making it easier to keep it updated.
- **Standardized**: Uses the OpenAPI 3.0 specification, compatible with many other tools (Postman, Insomnia, etc.).
Loading
Loading