Skip to content

Commit

Permalink
Merge pull request #80 from ghoshRitesh12/cache
Browse files Browse the repository at this point in the history
Add caching layer for improved performance
  • Loading branch information
ghoshRitesh12 authored Dec 7, 2024
2 parents 03771a2 + 02c8d10 commit b071818
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 52 deletions.
14 changes: 8 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
ANIWATCH_API_PORT=4000

# env to control allowed origins
ANIWATCH_API_CORS_ALLOWED_ORIGINS=https://your-production-domain.com,https://another-trusted-domain.com
ANIWATCH_API_CORS_ALLOWED_ORIGINS=<https://your-production-domain.com,https://another-trusted-domain.com>

# RATE LIMIT
# duration to track requests (in milliseconds) for rate limiting. here, 30*60*1000 = 1800000 = 30 minutes
Expand All @@ -12,12 +12,14 @@ ANIWATCH_API_MAX_REQS=70

# CAUTION:
# For personal deployments, if you wanna have rate limitting
# in your application, then set this env to your deployed
# instance's hostname, otherwise don't set or have this env at all.
# If you set this env to an incorrect value, you may face other issues.

# in your application, then set the env below to your deployed
# instance's hostname, otherwise don't set or have it at all.
# If you set the env below to an incorrect value, you may face other issues.
# ANIWATCH_API_HOSTNAME="api-aniwatch.onrender.com"


# NOTE: this env is "required" for vercel deployments
# ANIWATCH_VERCEL_DEPLOYMENT=<true or any non zero value>
# ANIWATCH_API_VERCEL_DEPLOYMENT=<true or any non zero value>

# env to use optional redis caching functionality
ANIWATCH_API_REDIS_CONN_URL=<rediss://default:[email protected]:6379>
60 changes: 36 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
# <p align="center">Aniwatch API</p>

<div align="center">
A free restful API serving anime information from <a href="https://hianime.to" target="_blank">hianime.to</a>
<br/><br/>
<strong>
A free RESTful API serving anime information from <a href="https://hianime.to" target="_blank">hianime.to</a>

<br/>

<div>
<a
href="https://github.com/ghoshRitesh12/aniwatch-api/issues/new?assignees=ghoshRitesh12&labels=bug&template=bug-report.yml"
>
Expand All @@ -28,7 +30,7 @@
>
Feature request
</a>
</strong>
</div>
</div>

<br/>
Expand Down Expand Up @@ -56,17 +58,19 @@
> [!IMPORTANT]
>
> 1. [https://api-aniwatch.onrender.com](https://api-aniwatch.onrender.com/) is only meant to demo the API and has rate-limiting enabled to minimise bandwidth consumption. It is recommended to deploy your own instance for personal use by customizing the api as you need it to be.
> 2. This API is just an unofficial api for [hianime.to](https://hianime.to) and is in no other way officially related to the same.
> 3. The content that this api provides is not mine, nor is it hosted by me. These belong to their respective owners. This api just demonstrates how to build an api that scrapes websites and uses their content.
> 1. [https://api-aniwatch.onrender.com](https://api-aniwatch.onrender.com/) is only meant to demo the API and has rate-limiting enabled to minimize bandwidth consumption. It is recommended to deploy your own instance for personal use by customizing the API as you need it to be.
> 2. This API is just an unofficial API for [hianime.to](https://hianime.to) and is in no other way officially related to the same.
> 3. The content that this API provides is not mine, nor is it hosted by me. These belong to their respective owners. This API just demonstrates how to build an API that scrapes websites and uses their content.
## Table of Contents

- [Installation](#installation)
- [Local](#local)
- [Docker](#docker)
- [Envs](#envs)
- [Host your instance](#-host-your-instance)
- [Configuration](#️configuration)
- [Custom HTTP Headers](#custom-http-headers)
- [Environment Variables](#environment-variables)
- [Host your instance](#host-your-instance)
- [Vercel](#vercel)
- [Render](#render)
- [Documentation](#documentation)
Expand Down Expand Up @@ -115,37 +119,45 @@

### Docker

Docker image is available at [GitHub Container Registry](https://github.com/ghoshRitesh12/aniwatch-api/pkgs/container/aniwatch).
The Docker image is available at [The GitHub Container Registry](https://github.com/ghoshRitesh12/aniwatch-api/pkgs/container/aniwatch).

Run the following commands to pull and run the docker image.

```bash
docker pull ghcr.io/ghoshritesh12/aniwatch
docker run -p 4000:4000 ghcr.io/ghoshritesh12/aniwatch
docker run -d --name aniwatch-api -p 4000:4000 ghcr.io/ghoshritesh12/aniwatch
```

The above command will start the server on port 4000. You can access the server at [http://localhost:4000](http://localhost:4000) and you can also change the port by changing the `-p` option to `-p <port>:4000`.
The above command will start the server on port 4000. You can access the server at [http://localhost:4000](http://localhost:4000), and you can also change the port by changing the `-p` option to `-p <port>:4000`.

The `-d` flag runs the container in detached mode, and the `--name` flag is used to name the container that's about to run.

## <span id="configuration">⚙️ Configuration</span>

### Custom HTTP Headers
Currently this API supports parsing of only one custom header, and more may be implemented in the future to accommodate varying needs.

You can also add the `-d` flag to run the container in detached mode.
- `X-ANIWATCH-CACHE-EXPIRY`: this custom header is used to specify the cache expiration duration in **seconds** (defaults to 60 if the header is missing). The `ANIWATCH_API_REDIS_CONN_URL` env is required for this custom header to function as intended; otherwise, there's no point in setting this custom header.

## <span id="envs">⚙️ Envs</span>
### Environment Variables

More info can be found in [`.env.example`](https://github.com/ghoshRitesh12/aniwatch-api/blob/main/.env.example) file
More info can be found in [`.env.example`](https://github.com/ghoshRitesh12/aniwatch-api/blob/main/.env.example) file, where envs's having a value that is contained within `<` `>` angled brackets are just examples and should be replaced with relevant ones.

- `ANIWATCH_API_PORT`: port number of the aniwatch api
- `ANIWATCH_API_WINDOW_MS`: duration to track requests for rate limitting (in milliseconds)
- `ANIWATCH_API_MAX_REQS`: maximum number of requests in the `ANIWATCH_API_WINDOW_MS` timeperiod
- `ANIWATCH_API_CORS_ALLOWED_ORIGINS`: allowed origins, separated by commas and no spaces in between
- `ANIWATCH_VERCEL_DEPLOYMENT`: required for distinguishing vercel deployment from other ones, set it to true of any other non-zero value
- `ANIWATCH_API_HOSTNAME`: set this to your api instance's hostname to enable rate limitting, don't have this value if you don't wish to rate limit
- `ANIWATCH_API_PORT`: port number of the aniwatch API.
- `ANIWATCH_API_WINDOW_MS`: duration to track requests for rate limiting (in milliseconds).
- `ANIWATCH_API_MAX_REQS`: maximum number of requests in the `ANIWATCH_API_WINDOW_MS` time period.
- `ANIWATCH_API_CORS_ALLOWED_ORIGINS`: allowed origins, separated by commas and no spaces in between.
- `ANIWATCH_API_VERCEL_DEPLOYMENT`: required for distinguishing Vercel deployment from other ones; set it to true or any other non-zero value.
- `ANIWATCH_API_HOSTNAME`: set this to your api instance's hostname to enable rate limiting, don't have this value if you don't wish to rate limit.
- `ANIWATCH_API_REDIS_CONN_URL`: this env is optional by default and can be set to utilize Redis caching functionality. It has to be a valid connection URL; otherwise, the Redis client can throw unexpected errors.

## <span id="host-your-instance">⛅ Host your instance</span>

> [!CAUTION]
>
> For personal deployments:
>
> - If you wanna have rate limitting in your application, then set the `ANIWATCH_API_HOSTNAME` env to your deployed instance's hostname, otherwise don't set or have this env at all. If you set this env to an incorrect value, you may face other issues.
> - If you want to have rate limiting in your application, then set the `ANIWATCH_API_HOSTNAME` env to your deployed instance's hostname; otherwise, don't set or have this env at all. If you set this env to an incorrect value, you may face other issues.
> - It's optional by default, but if you want to have endpoint response caching functionality, then set the `ANIWATCH_API_REDIS_CONN_URL` env to a valid Redis connection URL. If the connection URL is invalid, the Redis client can throw unexpected errors.
> - Remove the if block from the [`server.ts`](https://github.com/ghoshRitesh12/aniwatch-api/blob/main/src/server.ts) file, spanning from lines [71](https://github.com/ghoshRitesh12/aniwatch-api/blob/main/src/server.ts#L71) to [83](https://github.com/ghoshRitesh12/aniwatch-api/blob/main/src/server.ts#L83).
### Vercel
Expand All @@ -156,7 +168,7 @@ Deploy your own instance of Aniwatch API on Vercel.

> [!NOTE]
>
> When deploying to vercel, set an env named `ANIWATCH_VERCEL_DEPLOYMENT` to `true` or any non-zero value, but this env must be present.
> When deploying to vercel, set an env named `ANIWATCH_API_VERCEL_DEPLOYMENT` to `true` or any non-zero value, but this env must be present.
### Render

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"aniwatch": "^2.4.1",
"dotenv": "^16.4.5",
"hono": "^4.6.3",
"hono-rate-limiter": "^0.4.0"
"hono-rate-limiter": "^0.4.0",
"ioredis": "^5.4.1"
},
"devDependencies": {
"@types/node": "^22.7.4",
Expand Down
50 changes: 50 additions & 0 deletions src/config/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { config } from "dotenv";
import { Redis } from "ioredis";

config();

export class AniwatchAPICache {
private _client: Redis | null;
public isOptional: boolean = true;

static DEFAULT_CACHE_EXPIRY_SECONDS = 60 as const;
static CACHE_EXPIRY_HEADER_NAME = "X-ANIWATCH-CACHE-EXPIRY" as const;

constructor() {
const redisConnURL = process.env?.ANIWATCH_API_REDIS_CONN_URL;
this.isOptional = !Boolean(redisConnURL);
this._client = this.isOptional ? null : new Redis(String(redisConnURL));
}

set(key: string | Buffer, value: string | Buffer | number) {
if (this.isOptional) return;
return this._client?.set(key, value);
}

get(key: string | Buffer) {
if (this.isOptional) return;
return this._client?.get(key);
}

/**
* @param expirySeconds set to 60 by default
*/
async getOrSet<T>(
key: string | Buffer,
setCB: () => Promise<T>,
expirySeconds: number = AniwatchAPICache.DEFAULT_CACHE_EXPIRY_SECONDS
) {
const cachedData = this.isOptional
? null
: (await this._client?.get(key)) || null;
let data = JSON.parse(String(cachedData)) as T;

if (!data) {
data = await setCB();
await this._client?.set(key, JSON.stringify(data), "EX", expirySeconds);
}
return data;
}
}

export const cache = new AniwatchAPICache();
8 changes: 8 additions & 0 deletions src/config/variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type CacheVariables = {
CACHE_CONFIG: {
key: string;
duration: number;
};
};

export type AniwatchAPIVariables = {} & CacheVariables;
Loading

0 comments on commit b071818

Please sign in to comment.