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

# Node.js SDK

> Official Node.js SDK for Gately - JWT authentication for Express, Fastify, and any Node.js app

The **@gately/nodejs** SDK provides JWT-based authentication for Node.js backends with first-class support for Express and Fastify.

<Info>
  **Version:** @gately/nodejs\@2.2.0\
  **Features:** Token verification, Express middleware, Fastify plugin, token caching, TypeScript support
</Info>

## Installation

```bash theme={null}
npm install @gately/nodejs@latest
```

Or install a specific version:

```bash theme={null}
npm install @gately/nodejs@2.2.0
```

Get your API key: **[Dashboard → Settings → API Keys](https://www.usegately.com/dashboard?settings=api)**

## Quick Start

### Express

```typescript theme={null}
import express from 'express'
import { gatelyMiddleware } from '@gately/nodejs/express'

const app = express()

app.use(gatelyMiddleware({
  apiKey: process.env.GATELY_API_KEY!
}))

app.get('/api/me', (req, res) => {
  res.json({ user: req.auth?.user })
})

app.listen(3000)
```

### Fastify

```typescript theme={null}
import Fastify from 'fastify'
import { gatelyFastifyPlugin } from '@gately/nodejs/fastify'

const fastify = Fastify()

await fastify.register(gatelyFastifyPlugin, {
  apiKey: process.env.GATELY_API_KEY!
})

fastify.get('/api/me', async (request) => {
  return { user: request.auth?.user }
})

await fastify.listen({ port: 3000 })
```

### Manual Token Verification

```typescript theme={null}
import { verifyToken } from '@gately/nodejs'

const user = await verifyToken(token, process.env.GATELY_API_KEY!)

console.log(user.sub)    // user ID
console.log(user.email)  // user email
```

## Core API

### verifyToken(token, apiKey)

Verifies and decodes a Gately JWT token.

```typescript theme={null}
import { verifyToken, TokenVerificationError } from '@gately/nodejs'

try {
  const claims = await verifyToken(token, apiKey)
  // claims.sub     — user ID
  // claims.email   — user email
  // claims.apiKey  — your API key
  // claims.iat     — issued at (unix seconds)
  // claims.exp     — expires at (unix seconds)
} catch (err) {
  if (err instanceof TokenVerificationError) {
    // invalid, expired, or wrong apiKey
  }
}
```

### validateToken(token, apiKey)

Alias for `verifyToken`.

### getAuthorizationHeader(authHeader)

Extracts the JWT from an `Authorization` header.

```typescript theme={null}
import { getAuthorizationHeader } from '@gately/nodejs'

const token = getAuthorizationHeader(req.headers.authorization)
// Handles: "Bearer <token>", "bearer <token>", or raw token
```

## Express Integration

### Global Middleware

```typescript theme={null}
import { gatelyMiddleware } from '@gately/nodejs/express'

app.use(gatelyMiddleware({
  apiKey: process.env.GATELY_API_KEY!,
  optional: false,                        // default — require auth
  excludePaths: ['/health', '/status']    // skip auth on these paths
}))
```

### Per-Route: requireAuth

```typescript theme={null}
import { requireAuth } from '@gately/nodejs'

app.get('/api/profile', requireAuth(apiKey), (req, res) => {
  res.json({ user: req.auth?.user })
})
```

### Per-Route: optionalAuth

Attaches user to `req.auth` if token is present, but doesn't block if missing:

```typescript theme={null}
import { optionalAuth } from '@gately/nodejs'

app.get('/api/feed', optionalAuth(apiKey), (req, res) => {
  const isLoggedIn = !!req.auth?.user
  res.json({ personalised: isLoggedIn })
})
```

### Per-Route: withAuth

Attach a custom callback after verification:

```typescript theme={null}
import { withAuth } from '@gately/nodejs'

app.delete('/api/users/:id',
  withAuth(apiKey, (user) => {
    if (user.role !== 'admin') throw new Error('Admins only')
  }),
  (req, res) => res.json({ deleted: true })
)
```

### protect

Shorthand route-level protection:

```typescript theme={null}
import { protect } from '@gately/nodejs/express'

app.get('/api/secret', protect(apiKey), (req, res) => {
  res.json({ secret: '42' })
})
```

## Fastify Integration

### Plugin (Global)

```typescript theme={null}
import { gatelyFastifyPlugin } from '@gately/nodejs/fastify'

await fastify.register(gatelyFastifyPlugin, {
  apiKey: process.env.GATELY_API_KEY!,
  optional: false   // set true to make auth optional globally
})
```

### Route-Level Protection

```typescript theme={null}
import { fastifyProtect } from '@gately/nodejs/fastify'

const guard = await fastifyProtect(fastify, apiKey)

fastify.get('/api/admin',
  { onRequest: guard.onRequest },
  async (request) => {
    return { user: request.auth?.user }
  }
)
```

## Request Object

Middleware attaches an `auth` object to every authenticated request:

```typescript theme={null}
req.auth = {
  user: {
    sub: 'user_123',           // user ID
    email: 'user@example.com',
    apiKey: 'your_api_key',
    iat: 1694098800,
    exp: 1694102400,
    // ...any custom claims
  },
  token: 'eyJhbGc...'          // original JWT
}
```

## Utility Functions

### isTokenExpired

```typescript theme={null}
import { isTokenExpired } from '@gately/nodejs'

if (isTokenExpired(token)) {
  // token has expired
}
```

### getTokenExpiry

```typescript theme={null}
import { getTokenExpiry } from '@gately/nodejs'

const secondsLeft = getTokenExpiry(token)
if (secondsLeft && secondsLeft < 300) {
  // expires in less than 5 minutes
}
```

### Token Cache

Tokens are cached in memory to avoid re-decoding on every request:

```typescript theme={null}
import { clearTokenCache, getTokenCacheStats } from '@gately/nodejs'

// After revoking a token, clear cache
clearTokenCache()

// Check cache size
const { size, maxSize } = getTokenCacheStats()
```

## Error Handling

```typescript theme={null}
import {
  GatelyError,
  TokenVerificationError,
  UnauthorizedError,
  ForbiddenError
} from '@gately/nodejs'

app.get('/api/data', async (req, res) => {
  try {
    const token = getAuthorizationHeader(req.headers.authorization)
    if (!token) throw new UnauthorizedError('Token required')

    const user = await verifyToken(token, apiKey)
    res.json({ user })
  } catch (err) {
    if (err instanceof TokenVerificationError) {
      return res.status(401).json({ error: 'Invalid or expired token' })
    }
    if (err instanceof UnauthorizedError) {
      return res.status(401).json({ error: err.message })
    }
    if (err instanceof ForbiddenError) {
      return res.status(403).json({ error: err.message })
    }
    res.status(500).json({ error: 'Server error' })
  }
})
```

## Role-Based Access

```typescript theme={null}
function requireRole(role: string) {
  return (req: any, res: any, next: any) => {
    if (req.auth?.user?.role !== role) {
      return res.status(403).json({ error: 'Insufficient permissions' })
    }
    next()
  }
}

app.delete('/api/users/:id',
  requireAuth(apiKey),
  requireRole('admin'),
  handler
)
```

## TypeScript

```typescript theme={null}
import type {
  AuthConfig,
  VerifiedToken,
  MiddlewareOptions,
  AuthRequest
} from '@gately/nodejs'

const config: AuthConfig = {
  apiKey: process.env.GATELY_API_KEY!
}

const handler = (req: AuthRequest, res: any) => {
  const user: VerifiedToken = req.auth!.user
  res.json({ id: user.sub, email: user.email })
}
```

## Environment Variables

```bash theme={null}
# .env
GATELY_API_KEY=your_api_key_here
```

```typescript theme={null}
const apiKey = process.env.GATELY_API_KEY
if (!apiKey) throw new Error('GATELY_API_KEY is not set')
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Token verification fails">
    * Confirm the token was issued by Gately and hasn't expired
    * Ensure `apiKey` matches the project the token was issued for
    * Check the `Authorization` header format: `Bearer <token>`
  </Accordion>

  <Accordion title="Middleware not protecting routes">
    Middleware must be registered **before** route handlers:

    ```typescript theme={null}
    // ✅ Correct
    app.use(gatelyMiddleware({ apiKey }))
    app.get('/api/data', handler)

    // ❌ Wrong
    app.get('/api/data', handler)
    app.use(gatelyMiddleware({ apiKey }))
    ```
  </Accordion>

  <Accordion title="req.auth is undefined">
    Ensure the middleware ran successfully. Check that:

    * A valid `Authorization: Bearer <token>` header is sent
    * The route is not in `excludePaths`
    * `optional` is not set to `true` (which silently skips bad tokens)
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="React SDK" icon="react" href="/docs/sdk/react">
    Client-side authentication
  </Card>

  <Card title="Next.js SDK" icon="nextjs" href="/docs/sdk/nextjs">
    Full-stack Next.js auth
  </Card>
</CardGroup>

**Support:** [support@usegately.com](mailto:support@usegately.com) · [Slack Community](https://join.slack.com/t/usegately/shared_invite/zt-3llscjz41-PuVEY5Xu0M2DCi8WEojN0Q)
