# 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
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
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
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
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
curl http://localhost:3000/secret \
  -H "Authorization: Bearer YOUR_API_KEY"
```

You should see:

```json
{
  "message": "Welcome to the secret route!",
  "keyId": "key_...",
  "identity": null
}
```

Now try without a key:

```bash Test without key
curl http://localhost:3000/secret
```

You'll get:

```json
{
  "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
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
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
    npm install -D typescript @types/express @types/node
    ```

    ```ts
    import express from "express";
    import { Unkey } from "@unkey/api";
    ```
  </Accordion>
</AccordionGroup>
