> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sevalla.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Express

> This guide explains how to deploy a basic Express site to Sevalla.

[Express](https://expressjs.com/) is a minimalist and flexible web framework for Node.js, designed to simplify and streamline the building of APIs and web applications. It provides a lightweight routing system, middleware support, and a straightforward API that gives developers full control without unnecessary complexity. Express is widely used due to its ease of use, large ecosystem of plugins, and compatibility with virtually any Node.js architecture, making it a popular choice for both small projects and large-scale applications.

Express can only be used on Application Hosting; it cannot be deployed as a static site.

## Configuration

With Express, you can either compile TypeScript to JavaScript for production or run TypeScript directly.

### Compile to JavaScript (recommended for production)

* Sevalla will run `npm run build` followed by `npm start`
* Provides faster startup and better runtime performance

Example`package.json`:

```json theme={null}
{
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc",
    "dev": "tsx watch src/index.ts"
  }
}
```

### **Run TypeScript directly**

* Sevalla will run `npm start` using `tsx` or `ts-node`
* Simpler setup with no build step.

Example`package.json`:

```json theme={null}
{
  "scripts": {
    "start": "tsx src/index.ts",
    "dev": "tsx watch src/index.ts"
  }
}
```

## Containerization

### Dockerfile

The build for [**Dockerfiles**](https://docs.sevalla.com/applications/build-options/dockerfile) is fully customizable. We recommend the following best practices when using a Dockerfile for Express on Sevalla:

* **Use Alpine images** - Smaller image size (\~40MB vs \~900MB).
* **Multi-stage builds** - Separate build and runtime dependencies.
* **Layer caching** - Copy `package.json` before the source code.
* **Security** - Run as a non-root user in production.
* **.dockerignore** - Exclude unnecessary files.

The following is an example Dockerfile for Express:

```javascript expandable theme={null}
# Build stage
FROM node:lts-alpine AS builder

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci

COPY . .
RUN npm run build

# Production stage
FROM node:lts-alpine

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm ci --only=production

COPY --from=builder /app/dist ./dist

EXPOSE 3000

ENV PORT=3000
ENV HOST=0.0.0.0
ENV NODE_ENV=production

CMD ["node", "dist/index.js"]
```

### Nixpacks

[Nixpacks](https://docs.sevalla.com/applications/build-options/nixpacks) offers less customization than Dockerfiles, but you can still adjust the build process by creating a `nixpacks.toml` file and using [**Nixpacks-specific environment variables**](https://nixpacks.com/docs/configuration/environment). This allows you to fine-tune how dependencies are installed, how your application is built, and which runtime settings are applied.

You can control the Node.js version used during the build by using an `.nvmrc` file, the `engines` field in `package.json`, or the `NIXPACKS_NODE_VERSION` environment variable. If none are set, Nixpacks defaults to Node.js 18.

We recommend the following best practices when using Nixpacks for Express on Sevalla:

* **Commit lock files** - Always include `package-lock.json`, `yarn.lock`, or `pnpm-lock.yaml` for deterministic, reproducible builds.
* **Specify your Node.js version** - Use the `engines` field in `package.json` or an `.nvmrc` file to ensure consistent versioning.
* **Use npm ci** - Faster and more reliable than `npm install` in CI/CD environments.
* **Set** `NODE_ENV=production` - Helps optimize performance and reduce bundle size.
* **Test builds locally** - Use the Nixpacks CLI to identify build issues before deploying.
* **Keep configuration minimal** - Allow Nixpacks to auto-detect your setup; only add a `nixpacks.toml` file for advanced customization.
* **Optimize TypeScript builds** - After compiling, run `npm prune --production` to remove dev dependencies.
* **Define a** `start`**script** - Ensure `package.json` includes a `start` script for consistent server startup.

The following is an example `nixpacks.toml` configuration for Express:

```javascript theme={null}
# nixpacks.toml

[phases.setup]
nixPkgs = ["nodejs_22"]

[phases.install]
cmds = ["npm ci"]

[phases.build]
cmds = ["npm run build"]

[start]
cmd = "npm start"
```

### Buildpacks

[**Buildpacks**](https://docs.sevalla.com/applications/build-options/buildpacks), automatically detects your project’s lock file and installs dependencies using the appropriate package manager. When using Buildpacks, you cannot directly modify the underlying build phases or control how dependencies are installed; Buildpacks determine this based on your project’s structure and configuration.

However, you can influence the final runtime behavior by defining the appropriate `start` script in your `package.json `file, which Buildpacks will use when launching your Express application.

## CDN

Sevalla provides a premium, Cloudflare-powered CDN for Application Hosting at no additional cost. To get the most out of Sevalla’s CDN when deploying your Express application, we recommend the following best practices:

* [**Enable the CDN**](https://docs.sevalla.com/applications/cdn) for all production applications.
* Set appropriate `Cache-Control` headers on API routes to ensure proper caching behavior.
* [**Purge the CDN cache**](https://docs.sevalla.com/applications/cdn#clear-the-cdn-cache) after deploying critical updates to avoid serving stale content.
* Use versioned URLs for static assets, for example `/static/app.v123.js` .

### Optimizing Express for CDN

#### Static files with cache headers

```javascript theme={null}
// src/index.ts
import express from "express";
import path from "path";

const app = express();
const PORT = process.env.PORT || 3000;

// Serve static files with caching
app.use(
  "/static",
  express.static(path.join(__dirname, "../public"), {
    maxAge: "1y", // Cache for 1 year
    immutable: true,
    setHeaders: (res, filePath) => {
      res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
    },
  })
);

// API endpoint with shorter cache
app.get("/api/data", (req, res) => {
  const data = { message: "Hello from Express!" };

  res.setHeader("Content-Type", "application/json");
  res.setHeader("Cache-Control", "public, max-age=3600, s-maxage=3600");
  res.json(data);
});

// 404 handler
app.use((req, res) => {
  res.status(404).send("Not Found");
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

## Edge caching

[**Edge caching**](https://docs.sevalla.com/applications/edge-caching) stores your Sevalla site cache on Cloudflare’s 260+ global data centers, delivering responses from the location nearest to each visitor for faster performance. To maximize the benefits of Sevalla’s Edge Caching for your Express application, we recommend the following best practices:

* Set appropriate `Cache-Control` headers in loaders to control caching behavior.
* Combine edge caching with the CDN for a complete caching strategy.

### Optimizing Express for edge caching

```javascript theme={null}
// src/index.ts
import express from "express";

const app = express();
const PORT = process.env.PORT || 3000;

// API with edge caching
app.get("/api/products", (req, res) => {
  const products = getProducts(); // Your data fetching logic

  res.setHeader("Content-Type", "application/json");
  res.setHeader("Cache-Control", "public, max-age=60, s-maxage=3600");
  res.json(products);
});

// User-specific data (don't cache)
app.get("/api/user/profile", (req, res) => {
  const userData = { user: "data" };

  res.setHeader("Content-Type", "application/json");
  res.setHeader("Cache-Control", "private, no-cache");
  res.json(userData);
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

#### `Cache-Control`

With Sevalla’s Cloudflare integration,`Cache-Control`headers are respected at the edge, giving you precise control over how content is cached and served globally. The following are some common directives you can use in your Express application:

* `public, s-maxage=3600` - Caches the response on the CDN for 1 hour, improving performance for frequently accessed content.
* `public, max-age=31536000, immutable` - Ideal for versioned static assets, allowing them to be cached for up to 1 year with no revalidation.
* `private` - Prevents CDN caching and ensures the response is only cached by the end user’s browser, for personalized or sensitive data.

<Info>
  Sevalla does not yet support the `stale-while-revalidate` Cache-Control directive. To prevent unexpected caching behavior, we recommend not using this directive in your API or asset caching settings.
</Info>

## Health checks

Ensure your application remains available during deployments by implementing health checks:

* Always implement [**health checks**](https://docs.sevalla.com/applications/processes#health-checks) for production applications.
* Keep checks lightweight; responses should complete in under 1 second.
* Verify critical dependencies (e.g., databases, Redis) as part of the checks.
* Return 200 for degraded states to allow deployments to continue smoothly.
* Return 503 only for critical failures that require pod restarts.

### Basic health check

```javascript theme={null}
// src/index.ts
import express from "express";

const app = express();
const PORT = process.env.PORT || 3000;

app.get("/api/health", (req, res) => {
  res.json({
    status: "ok",
    timestamp: new Date().toISOString(),
    runtime: "Node.js",
    version: process.version,
  });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

## Graceful shutdown

[**​**](https://docs.sevalla.com/quick-starts/javascript/fastify#configuration)Implement graceful shutdown to cleanly close connections during deployments.

The following example demonstrates how to implement a graceful shutdown in an Express server by closing HTTP server and database connections safely upon receiving deployment or termination signals:

```javascript theme={null}
// src/index.ts
import express from "express";
import { Server } from "http";
import { pool } from "./lib/db";

const app = express();
const PORT = process.env.PORT || 3000;

// ... your routes

const server: Server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

// Graceful shutdown
process.on("SIGTERM", async () => {
  console.log("Received SIGTERM, closing connections...");

  // Stop accepting new connections
  server.close(async () => {
    console.log("HTTP server closed");

    // Close database pool
    await pool.end();

    console.log("Server closed gracefully");
    process.exit(0);
  });

  // Force close after 30 seconds
  setTimeout(() => {
    console.error("Forced shutdown after timeout");
    process.exit(1);
  }, 30000);
});

process.on("SIGINT", async () => {
  console.log("Received SIGINT, closing connections...");
  server.close(async () => {
    await pool.end();
    process.exit(0);
  });
});
```
