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

# Express

> Add API key authentication to your Express API using Unkey. Protect routes by verifying keys on every incoming request with minimal setup.

## What you'll build

An Express server with a protected `/secret` route that requires a valid API key. Requests without a valid key get rejected with a 401.

**Time to complete:** \~5 minutes

## Prerequisites

* [Unkey account](https://app.unkey.com/auth/sign-up) (free)
* [Keyspace created](https://app.unkey.com/apis) in your Unkey dashboard
* Node.js 18+

<Card title="Want to skip ahead?" icon="github" href="https://github.com/unkeyed/examples/tree/main/express">
  Clone the complete example and run it locally.
</Card>

<Steps titleSize="h3">
  <Step title="Create your Express app">
    ```bash theme={"theme":"kanagawa-wave"}
    mkdir unkey-express && cd unkey-express
    npm init -y
    npm install express @unkey/api dotenv
    ```
  </Step>

  <Step title="Add your root key">
    Get a root key from [Settings → Root Keys](https://app.unkey.com/settings/root-keys) and create a `.env` file:

    ```bash .env theme={"theme":"kanagawa-wave"}
    UNKEY_ROOT_KEY="unkey_..."
    ```

    <Warning>Never commit your `.env` file. Add it to `.gitignore`.</Warning>
  </Step>

  <Step title="Create your server">
    Create `index.js` with a protected route:

    ```js index.js theme={"theme":"kanagawa-wave"}
    const express = require("express");
    const { Unkey } = require("@unkey/api");
    require("dotenv").config();

    const app = express();
    const port = process.env.PORT || 3000;
    const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY });

    // Public route
    app.get("/", (req, res) => {
      res.json({ message: "Welcome! Try /secret with an API key." });
    });

    // Protected route
    app.get("/secret", async (req, res) => {
      // 1. Extract the key from the Authorization header
      const authHeader = req.headers.authorization;
      const key = authHeader?.replace("Bearer ", "");

      if (!key) {
        return res.status(401).json({ error: "Missing API key" });
      }

      // 2. Verify with Unkey
      try {
        const { data } = await unkey.keys.verifyKey({ key });

        if (!data.valid) {
          // Key is invalid, expired, rate limited, etc.
          return res.status(401).json({
            error: "Invalid API key",
            code: data.code,
          });
        }

        // 3. Key is valid, return protected data
        res.json({
          message: "Welcome to the secret route!",
          keyId: data.keyId,
          // Include any metadata you attached to the key
          identity: data.identity,
        });
      } catch (err) {
        console.error(err);
        return res.status(500).json({ error: "Could not verify key" });
      }
    });

    app.listen(port, () => {
      console.log(`Server running at http://localhost:${port}`);
    });
    ```
  </Step>

  <Step title="Start your server">
    ```bash theme={"theme":"kanagawa-wave"}
    node index.js
    ```
  </Step>

  <Step title="Test it">
    First, create a test key in your [Unkey dashboard](https://app.unkey.com), then:

    ```bash Test with valid key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000/secret \
      -H "Authorization: Bearer YOUR_API_KEY"
    ```

    You should see:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "message": "Welcome to the secret route!",
      "keyId": "key_...",
      "identity": null
    }
    ```

    Now try without a key:

    ```bash Test without key theme={"theme":"kanagawa-wave"}
    curl http://localhost:3000/secret
    ```

    You'll get:

    ```json theme={"theme":"kanagawa-wave"}
    {
      "error": "Missing API key"
    }
    ```
  </Step>
</Steps>

## What's in `data`?

After successful verification, `data` contains:

| Field         | Type        | Description                                                         |
| ------------- | ----------- | ------------------------------------------------------------------- |
| `valid`       | `boolean`   | Whether the key passed all checks                                   |
| `code`        | `string`    | Status code (`VALID`, `NOT_FOUND`, `RATE_LIMITED`, etc.)            |
| `keyId`       | `string`    | The key's unique identifier                                         |
| `name`        | `string?`   | Human-readable name of the key                                      |
| `meta`        | `object?`   | Custom metadata associated with the key                             |
| `expires`     | `number?`   | Unix timestamp (in milliseconds) when the key will expire. (if set) |
| `credits`     | `number?`   | Remaining uses (if usage limits set)                                |
| `enabled`     | `boolean`   | Whether the key is enabled                                          |
| `roles`       | `string[]?` | Permissions attached to the key                                     |
| `permissions` | `string[]?` | Permissions attached to the key                                     |
| `identity`    | `object?`   | Identity info if `externalId` was set when creating the key         |
| `ratelimits`  | `object[]?` | Rate limit states (if rate limiting configured)                     |

## Using as middleware

For cleaner code, extract verification into middleware:

```js middleware/auth.js theme={"theme":"kanagawa-wave"}
const { Unkey } = require("@unkey/api");

const unkey = new Unkey({ rootKey: process.env.UNKEY_ROOT_KEY });

async function unkeyAuth(req, res, next) {
  const key = req.headers.authorization?.replace("Bearer ", "");

  if (!key) {
    return res.status(401).json({ error: "Missing API key" });
  }

  try {
    const { data } = await unkey.keys.verifyKey({ key });

    if (!data.valid) {
      return res.status(401).json({ error: "Invalid API key" });
    }

    // Attach key info to request for use in route handlers
    req.unkey = data;
    next();
  } catch (err) {
    console.error(err);
    return res.status(500).json({ error: "Could not verify key" });
  }
}

module.exports = { unkeyAuth };
```

Then use it on any route:

```js theme={"theme":"kanagawa-wave"}
const { unkeyAuth } = require("./middleware/auth");

app.get("/secret", unkeyAuth, (req, res) => {
  // req.unkey contains the verification result
  res.json({ message: "Secret data", keyId: req.unkey.keyId });
});

app.get("/another-secret", unkeyAuth, (req, res) => {
  res.json({ data: "More protected content" });
});
```

## Next steps

<CardGroup cols={2}>
  <Card title="Add rate limiting" icon="gauge-high" href="/platform/apis/features/ratelimiting/overview">
    Limit requests per key
  </Card>

  <Card title="Set usage limits" icon="calculator" href="/platform/apis/features/remaining">
    Cap total requests per key
  </Card>

  <Card title="Add permissions" icon="user-shield" href="/platform/apis/features/authorization/introduction">
    Fine-grained access control
  </Card>

  <Card title="SDK Reference" icon="book" href="/libraries/ts/api">
    Full TypeScript SDK docs
  </Card>
</CardGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Getting 401 even with a valid key?">
    * Ensure the key hasn't expired or been revoked - Verify the `Authorization`
      header format: `Bearer YOUR_KEY` (note the space) - Check that your root key
      has the `verify_key` permission
  </Accordion>

  <Accordion title="Getting 500 errors?">
    * Check that `UNKEY_ROOT_KEY` is set correctly in your `.env` - Make sure
      you're calling `require("dotenv").config()` before using env vars - Check the
      Unkey dashboard for any service issues
  </Accordion>

  <Accordion title="TypeScript version?">
    The code above uses CommonJS. For TypeScript, install types and use imports:

    ```bash theme={"theme":"kanagawa-wave"}
    npm install -D typescript @types/express @types/node
    ```

    ```ts theme={"theme":"kanagawa-wave"}
    import express from "express";
    import { Unkey } from "@unkey/api";
    ```
  </Accordion>
</AccordionGroup>
