Skip to main content

Routing

EverShop uses a file-based routing system with route.json configuration files. These files define how API endpoints are accessed and control the middleware execution order.

route.json Files

Every API endpoint in EverShop requires a route.json file that specifies:
  • HTTP methods allowed
  • URL path
  • Access control (public/private)

Basic Structure

route.json
{
  "methods": ["GET", "POST"],
  "path": "/api/endpoint",
  "access": "public"
}

Configuration Properties

methods

HTTP methods that the endpoint accepts:
{
  "methods": ["POST"]
}
{
  "methods": ["GET", "POST", "PUT", "DELETE"]
}
Only the methods listed in methods will be accepted. Other methods will return a 405 Method Not Allowed error.

path

The URL path where the endpoint is accessible:
{
  "path": "/foos"
}
{
  "path": "/api/products/:id"
}
Use Express-style path parameters with :paramName for dynamic routes.

access

Controls authentication requirements:
{
  "access": "public"   // No authentication required
}
{
  "access": "private"  // Authentication required
}

Middleware Execution Order

EverShop uses filename conventions to control middleware execution order. Files are executed alphabetically, and you can use brackets [name] to specify dependencies.

Naming Convention

Format: [prerequisite]handlerName.ts The name in brackets runs before the main handler.

Example: Body Parser Middleware

From the sample extension:
extensions/sample/src/api/createFoo/
├── route.json
├── bodyParser.ts
└── [bodyParser]createFoo.ts
1
First: bodyParser.ts runs
2
import bodyParser from 'body-parser';

export default (request, response, next) => {
  bodyParser.json({ inflate: false })(request, response, next);
};
3
Then: [bodyParser]createFoo.ts runs
4
import { EvershopRequest, EvershopResponse } from '@evershop/evershop';

export default (request: EvershopRequest, response: EvershopResponse, next) => {
  const { name, description } = request.body;  // Body is now parsed!

  if (!name || !description) {
    return response
      .status(400)
      .json({ error: 'Name and description are required' });
  }

  const newFoo = {
    id: Date.now(),
    name,
    description
  };

  response.status(201).json({
    success: true,
    data: { foo: newFoo }
  });
};
5
Configure the route
6
{
  "methods": ["POST"],
  "path": "/foos",
  "access": "private"
}

Complete API Endpoint Example

Here’s the full structure for the createFoo endpoint:
extensions/sample/src/api/createFoo/
├── route.json                    # Route configuration
├── bodyParser.ts                 # Middleware: parse JSON body
└── [bodyParser]createFoo.ts      # Handler: create new foo
{
  "methods": ["POST"],
  "path": "/foos",
  "access": "private"
}

Middleware Chain Examples

Single Middleware

api/getProduct/
├── route.json
└── getProduct.ts
No dependencies, just a simple handler.

Two Middleware

api/createOrder/
├── route.json
├── validateInput.ts
└── [validateInput]createOrder.ts
Execution order:
  1. validateInput.ts - Validates request data
  2. [validateInput]createOrder.ts - Creates the order

Three Middleware

api/updateProduct/
├── route.json
├── authenticate.ts
├── [authenticate]validatePermissions.ts
└── [authenticate][validatePermissions]updateProduct.ts
Execution order:
  1. authenticate.ts - Verify user is logged in
  2. [authenticate]validatePermissions.ts - Check user has permission
  3. [authenticate][validatePermissions]updateProduct.ts - Update the product
Middleware in brackets must exist as separate files. The files are executed in dependency order, not alphabetically.

Request and Response Types

EverShop provides TypeScript types for Express request and response objects:
import { EvershopRequest, EvershopResponse } from '@evershop/evershop';

export default (
  request: EvershopRequest, 
  response: EvershopResponse, 
  next
) => {
  // Access request properties
  const { body, params, query, headers } = request;
  
  // Send responses
  response.status(200).json({ success: true });
  response.status(400).json({ error: 'Bad request' });
  response.status(500).json({ error: 'Server error' });
  
  // Call next middleware
  next();
};

Common Patterns

Public API Endpoint

route.json
{
  "methods": ["GET"],
  "path": "/api/products",
  "access": "public"
}
Anyone can access this endpoint without authentication.

Private API Endpoint

route.json
{
  "methods": ["POST"],
  "path": "/api/orders",
  "access": "private"
}
Requires authentication. EverShop automatically checks for valid session/token.

RESTful Resource

extensions/myExtension/src/api/
├── getProducts/
│   ├── route.json          # GET /api/products
│   └── getProducts.ts
├── getProduct/
│   ├── route.json          # GET /api/products/:id
│   └── getProduct.ts
├── createProduct/
│   ├── route.json          # POST /api/products
│   └── createProduct.ts
├── updateProduct/
│   ├── route.json          # PUT /api/products/:id
│   └── updateProduct.ts
└── deleteProduct/
    ├── route.json          # DELETE /api/products/:id
    └── deleteProduct.ts

Page Routing

While API endpoints use route.json, page components use a different system based on directory structure and the layout export.

Page Areas

Pages are organized by “areas” - logical groupings based on where/when they render:
src/pages/
├── all/              # Renders on all pages
├── homepage/         # Renders on homepage only
├── productPage/      # Renders on product detail pages
├── checkout/         # Renders on checkout pages
└── account/          # Renders on account pages

Layout Export

The layout export determines where a component renders:
export const layout = {
  areaId: 'content',     // Which area to render in
  sortOrder: 10          // Order within that area
};
Lower sortOrder numbers render first. Use multiples of 10 (10, 20, 30) to leave room for insertions.

Common Area IDs

Area IDDescription
headerTop of all pages
footerBottom of all pages
contentMain content area
sidebarSidebar area
productPageBottomBelow product details
checkoutPaymentMethodPayment method selection
checkoutShippingShipping method selection

Best Practices

Name your API directories and files clearly:
  • Good: createOrder, updateProduct, deleteUser
  • Bad: handler, endpoint1, api
Create separate validation middleware:
api/createProduct/
├── route.json
├── validateProduct.ts
└── [validateProduct]createProduct.ts
Return appropriate HTTP status codes:
// 400 - Bad request (validation errors)
response.status(400).json({ error: 'Invalid input' });

// 401 - Unauthorized (not logged in)
response.status(401).json({ error: 'Authentication required' });

// 403 - Forbidden (logged in but no permission)
response.status(403).json({ error: 'Permission denied' });

// 404 - Not found
response.status(404).json({ error: 'Resource not found' });

// 500 - Server error
response.status(500).json({ error: 'Internal server error' });
Always import and use EverShop types:
import { EvershopRequest, EvershopResponse } from '@evershop/evershop';

type RequestBody = {
  name: string;
  description: string;
};

export default (
  request: EvershopRequest<RequestBody>,
  response: EvershopResponse,
  next
) => {
  // TypeScript knows request.body.name is a string
};

Testing Routes

Using curl

# POST request
curl -X POST http://localhost:3000/foos \
  -H "Content-Type: application/json" \
  -d '{"name":"Test","description":"A test foo"}'

# GET request
curl http://localhost:3000/api/products

# With authentication
curl http://localhost:3000/api/orders \
  -H "Authorization: Bearer YOUR_TOKEN"

Using Postman or Insomnia

  1. Set the HTTP method (GET, POST, etc.)
  2. Enter the URL: http://localhost:3000/foos
  3. Add headers (e.g., Content-Type: application/json)
  4. Add body (for POST/PUT requests)
  5. Send the request

Next Steps

  • Learn about Extensions for organizing your code
  • Understand GraphQL API for alternative data fetching
  • Explore Themes for page component rendering