# Route Directives

Directives are declarative decorators available to route definitions. They provide additional functionality to the route definitions.

There are a number of active directives available below:

1. Caching
2. Rate Limiting
3. Elevated Privileges
4. Idempotency
5. Verified
6. Subscribed

## Caching

Routes can be cached by adding this directive in the route definition.

<pre class="language-typescript" data-title="app/api/accounts/index.ts" data-line-numbers><code class="lang-typescript">export default async function Route (instance: FastifyInstance) {
    instance.get('/api/v1/account', handler);
    <a data-footnote-ref href="#user-content-fn-1">instance.cached();</a>
    
    async function handler(request, reply) {
        console.log(request.session);
        reply.status(204);
    }
}
</code></pre>

The directive adds an additional header to the response based on the cached behaviour

`x-cache: hit` if the content is cached

`x-cache: miss` if the content is supposed to be cached, but wasnt.

### `instance.cached()`

The default behaviour and will cache the request based on the user, url, and session of the user.

### ``instance.cached({ key: (request) ⇒ `custom-id` })``&#x20;

This overrides the key assigned in the caching table. The current request is available for use to derive a specific id.

### `instance.cached({ ttl: 60 })`

This changes the ttl (Time to live) of the cached object. This ttl is `5`seconds by default.

### `await reply.uncache(key: string, options?: CacheOptions)`

This is a decorator to reply that you can use to manually purge a cached response.

### `CacheOptions`

* ttl - Time to live of the cached object. default`5`seconds
* key - (request: Request) function to generate a cache key

## Rate Limiting

Rate limiting is provided by <https://github.com/fastify/fastify-rate-limit>

### `instance.throttled(limit: number, window: string, options?: RateLimitOptions)`

This is a separate directive that has a different signature from the throttling shown after. You can use it like any other directive

{% code title="app/api/route/index.ts" lineNumbers="true" %}

```typescript
export default function Route(instance: FastifyInstance) {
    instance.get('/api/v1/account', handler);
    instance.throttled();
    
    async function handler(request, reply) {
        reply.status(204);
    }
}
```

{% endcode %}

### `await request.throttled(key: string, limit?: number, options?: RateLimitOptions)`

### `await request.throttled(limit: number, options?: RateLimitOptions)`

The signature for throttled allow different usage for convenience.

{% code title="app/api/route/index.ts" lineNumbers="true" %}

```typescript
export default function Route(instance: FastifyInstance) {
    instance.get('/api/v1/account', handler);
    
    async function handler(request, reply) {
        await request.throttled('throttled');
        reply.status(204);
    }
}
```

{% endcode %}

### `await request.unthrottle(key?: string, options?: RateLimitOptions)`

If you need to explicitly reset the rate limit for a specific key.&#x20;

{% code title="app/api/route/index.ts" lineNumbers="true" %}

```typescript
export default function Route(instance: FastifyInstance) {
    instance.get('/api/v1/account', handler);
    
    async function handler(request, reply) {
        await request.throttled('throttled');
        reply.status(204);
        await request.unthrottle('throttled');
    }
}
```

{% endcode %}

Example usage: [auth-passwordless/index.ts](https://github.com/madewithnovel/novel/blob/main/app/api/internal/v1/auth-passwordless/index.ts#L25)

### `RateLimitOptions`

* timeWindow - the time in seconds on how long the rate limit should last
* max - the maximum amount before the throttling kicks in
* session - custom option used in generating the key

## Elevated Privileges

If you require actions to be verified by the user again, you can make use of the sudo middleware that exposes a password check before letting the action go through.

### `instance.sudo()`

<pre class="language-typescript" data-title="app/api/route/index.ts" data-line-numbers><code class="lang-typescript">export default async function Route(instance: FastifyInstance) {
    instance.post('/api/v1/accounts', handler);
    <a data-footnote-ref href="#user-content-fn-2">instance.sudo();</a>
    
    async function handler(request, reply) {
        console.log(request);
        reply.status(204);
    }
}
</code></pre>

{% hint style="warning" %}
**Important Note**

You need to add this piece of code in your schema so it can be picked up by Novel Web
{% endhint %}

{% code title="app/api/route/schema.json" lineNumbers="true" %}

```json
{
    ...rest of your schema.json
    "body": {
	"type": "object",
	"required": [...other fields, "sudo_password"],
	"properties": {
		...other fields
		"sudo_password": {
			"type": "string"
		}
	}
    }
}
```

{% endcode %}

See [Schema →](https://docs.novel.dev/novel-server/routing/schema)

## Idempotency

If you want to enable idempotency for a route. You can add the idempotency directive for that definition

{% code title="app/api/route/index.ts" lineNumbers="true" %}

```typescript
export default async function Route(instance: FastifyInstance) {
    instance.post('/api/v1/accounts', handler);
    instance.idempotent();
    
    async function handler(request, reply) {
        // or await request.idempotent();
        console.log(request.headers['idempotency-key']);
        reply.status(204);
    }
}
```

{% endcode %}

Requests to this endpoint need to have a unique `idempotency-key`header generated by the client.

{% code lineNumbers="true" %}

```javascript
await fetch('/api/v1/accounts', {
    method: 'POST',
    headers: {
        'idempotency-key': uuid.v1(),
    },
});
```

{% endcode %}

### `instance.idempotent(options?: IdempotencyOptions)`

If the endpoint has processed the request and a similar request with the same idempotency key is sent, it will return the response of the previous action.

### `IdempotencyOptions`

* ttl - the amount of time before the idempotency key can be reused. default 86400

## Verified

### `instance.verified()`

Check if the currently logged in user has a `verified`status.

{% code title="app/api/v1/your-route/index.ts" lineNumbers="true" %}

```typescript
export default function Route(instance) {
    instance.authorized();
    instance.verified();
    instance.get('/your/route', handler);
    
    async function handler (request, reply) {
        reply.send('ONLY FOR AUTHENTICATED API KEYS');
    }
}
```

{% endcode %}

## Subscribed

### `instance.subscribed()`

Check if the currently logged in organization has an active subscription.

{% code title="app/api/v1/your-route/index.ts" lineNumbers="true" %}

```typescript
export default function Route(instance) {
    instance.authorized();
    instance.subscribed();
    instance.get('/your/route', handler);
    
    async function handler (request, reply) {
        reply.send('ONLY FOR SUBSCRIBED ORGANIZATIONS');
    }
}
```

{% endcode %}

## Changelog

* 2024-12-20 - Initial Documentation

[^1]: This is the caching directive

[^2]: will look for `sudo_password`in the request body.
