Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const fastify = Fastify().register(dynamodbCache, {
tableName: "fastify-dynamodb-cache", // DynamoDB table name
defaultTTLSeconds: 30, // Default TTL (seconds), which would be used if no TTL is specified on the endpoint.
disableCache: true, // Optional! If you want to disable caching from being set on endpoints, you can set this to true. Set it to false or leave it empty to enable cache.
passthroughQueryParam: "_t", // Optional! If you want to define a query parameter for all endpoints, which will be used to bypass the cache (will set 'x-cache: ignored').
});

fastify.get(
Expand Down
11 changes: 11 additions & 0 deletions src/helpers/hasQueryParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const hasQueryParam = (
query: unknown,
key: string
): query is Record<string, unknown> => {
return (
typeof query === "object" &&
query !== null &&
!Array.isArray(query) &&
key in query
);
};
11 changes: 11 additions & 0 deletions src/hooks/onRequest.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
import { FastifyRequest, FastifyReply } from "fastify";
import { onRequestAsyncHookHandler } from "fastify/types/hooks";
import { hasQueryParam } from "../helpers/hasQueryParams";

interface CreateOnRequestHookOptions {
dynamoClient: DynamoDBClient;
tableName: string;
passthroughQueryParam?: string;
}

export const createOnRequestHook = ({
dynamoClient,
tableName,
passthroughQueryParam,
}: CreateOnRequestHookOptions) => {
const onRequestHook: onRequestAsyncHookHandler = async (
request: FastifyRequest,
Expand All @@ -22,6 +25,14 @@ export const createOnRequestHook = ({
},
});

if (
passthroughQueryParam &&
hasQueryParam(request.query, passthroughQueryParam)
) {
reply.header("x-cache", "ignored");
return;
}

try {
const { Item } = await dynamoClient.send(command);

Expand Down
49 changes: 47 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,50 @@ import fastifyPlugin from "fastify-plugin";
import { createOnRequestHook } from "./hooks/onRequest";
import { createOnSendHook } from "./hooks/onSend";

export interface PluginOptions {
/**
* Plugin options for fastify-aws-dynamodb-cache.
*
* @property {string} dynamoDbRegion - AWS region for DynamoDB client configuration.
* @property {string} [dynamoDbAddress] - Optional custom endpoint (useful for local development/testing).
* @property {string} tableName - DynamoDB table name used for storing cache entries.
* @property {number} defaultTTLSeconds - Global default TTL (in seconds) for all cache entries.
* @property {boolean} [disableCache=false] - If true, disables caching plugin-wide.
* @property {string} [passthroughQueryParam] - If defined, this query parameter will bypass cache when present in a request.
Comment on lines +10 to +15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between wrapping the variable in "[]" and not doing it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a way to say that value is optional

*/
export interface DynamodbCachePluginOptions {
dynamoDbRegion: string;
dynamoDbAddress?: string;
tableName: string;
defaultTTLSeconds: number;
disableCache?: boolean;
passthroughQueryParam?: string;
}

export const dynamodbCache: FastifyPluginAsync<PluginOptions> = (
/**
* Fastify plugin for caching responses in DynamoDB.
*
* Adds support for per-route caching via a shared DynamoDB table.
*
* Use route-level `config.cache` to control TTL and caching behavior:
*
* @example
* ```ts
* fastify.get('/my-route', {
* config: {
* cache: {
* cacheEnabled: true,
* ttlSeconds: 120
* }
* }
* }, async (req, reply) => {
* return { hello: 'world' };
* });
* ```
*
* @param fastify - Fastify instance
* @param opts - Plugin options for DynamoDB caching
*/
export const dynamodbCache: FastifyPluginAsync<DynamodbCachePluginOptions> = (
fastify,
opts
) => {
Expand All @@ -31,6 +66,7 @@ export const dynamodbCache: FastifyPluginAsync<PluginOptions> = (
const onRequestHook = createOnRequestHook({
dynamoClient,
tableName: opts.tableName,
passthroughQueryParam: opts.passthroughQueryParam,
});

const onSendHook = createOnSendHook({
Expand Down Expand Up @@ -63,8 +99,17 @@ export const dynamodbCache: FastifyPluginAsync<PluginOptions> = (

declare module "fastify" {
interface FastifyContextConfig {
/**
* Route-specific cache configuration.
*/
cache?: {
/**
* Enable or disable cache for this route. Defaults to false.
*/
cacheEnabled?: boolean;
/**
* TTL in seconds for the cached response. Overrides global defaultTTLSeconds if set.
*/
ttlSeconds?: number;
};
}
Expand Down