Animekun-db-cf is a Cloudflare Workers backend API for @Subhajeetch/Animekun-frontend , providing user authentication, profile management, and password recovery functionality powered by Cloudflare D1 database.
This project is divided into three repositories:
- Frontend – @Subhajeetch/Animekun-frontend
- Backend – @Subhajeetch/Animekun-backend
- Database – Cloudflare D1 @Subhajeetch/Animekun-db-cf (You are currently viewing this repository)
- Node.js 18.x or higher
- Wrangler CLI (
npm install -g wrangler) - Cloudflare account with D1 database setup
- Clone the repository:
git clone https://github.com/Subhajeetch/animekun-db-cf
cd animekun-db-cf- Install dependencies:
npm install-
Configure
wrangler.tomlusing wrangler.toml.example:- Update
database_idwith your Cloudflare D1 database ID
- Update
-
Set up environment variables by creating a
mine.env.jsfile: example file: mine.env.js.exampleconst env = { BREVO_API_KEY: "your_brevo_api_key_here", SECRET_KEY: "your_secret_key_here", REFRESH_SECRET_KEY: "your_refresh_secret_key_here", SENDER_EMAIL: "your_sender_email@example.com", ALLOWED_ORIGINS: [ "http://localhost:3000", "http://localhost:5173", "https://yourdomain.com", ], }; export default env;
Environment Variables:
BREVO_API_KEY- API key from Brevo for email functionalitySECRET_KEY- Secret key for JWT token signingREFRESH_SECRET_KEY- Secret key for refresh token signingSENDER_EMAIL- Email address for sending OTP and notifications (must be verified on Brevo.com)ALLOWED_ORIGINS- Array of allowed CORS origins
Getting a Brevo API Key:
- Go to Brevo.com
- Sign up or log in to your account
- Navigate to Settings → SMTP & API
- Under API Keys, click Create a new API key
- Give it a name (e.g., "Animekun API")
- Copy the API key and paste it in your
mine.env.jsfile asBREVO_API_KEY
Run the local development server:
npm startThe server will start on localhost:8787.
Deploy to Cloudflare:
wrangler deployAll API endpoints accept JSON request bodies and return JSON responses. The base URL for all endpoints is your Cloudflare Workers URL.
Successful responses:
{
"success": true,
"data": {}
}Error responses:
{
"success": false,
"message": "Error description"
}Create a new user account.
/sign-up| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
userName |
string | Unique username for the account (case-insensitive, minimum 4 characters). | Yes | -- |
displayName |
string | Display name for the user profile. | Yes | -- |
email |
string | User email address (must be unique and valid email format). | Yes | -- |
password |
string | User password (will be hashed using bcryptjs). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/sign-up", {
userName: "animefan123",
displayName: "Anime Fan",
email: "fan@example.com",
password: "securePassword123",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
message: "User created successfully",
accessToken: string,
refreshToken: string,
user: {
userId: number,
userName: string,
displayName: string,
email: string
}
}Error Responses:
400- Missing required fields409- Username or email already exists
Authenticate user with username/email and password.
/login| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
identifier |
string | Username or email address (case-insensitive). | Yes | -- |
password |
string | User password. | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/login", {
identifier: "animefan123",
password: "securePassword123",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
message: "Login successful",
accessToken: string,
refreshToken: string,
user: {
userId: number,
userName: string,
email: string
}
}Error Responses:
400- Missing credentials401- Invalid username/email or password
Validate if a username is available.
/check-username| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
userName |
string | Username to check (case-insensitive, minimum 4 characters). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/check-username", {
userName: "newusername",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200 - Available):
{
success: true,
isUsernameExist: false,
message: "Great! Username is unique."
}Success (409 - Already Taken):
{
success: false,
isUsernameExist: true,
message: "This username is taken. Sad!"
}Error Responses:
400- Username is required or less than 4 characters409- Username already exists
Retrieve authenticated user information using access or refresh token.
/get-user| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
accessToken |
string | JWT access token (optional, used first if available). | No | -- |
refreshToken |
string | JWT refresh token (optional, used if access token is invalid or expired). | No | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/get-user", {
accessToken: "your_access_token_here",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
user: {
userId: number,
userName: string,
displayName: string,
email: string,
role: string,
joinedDate: number,
profilePicture: string,
coverPicture: string,
bio: string,
pronouns: string,
watchHours: number,
watchedAnimes: number,
activityStatus: string
},
newAccessToken: string // Only if refreshed
}Error Responses:
400- Missing both accessToken and refreshToken401- Not authenticated, invalid or expired tokens
Retrieve public user profile information.
/get-profile| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
username |
string | Username of the profile to retrieve (case-insensitive). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/get-profile", {
username: "animefan123",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
user: {
userId: number,
userName: string,
displayName: string,
email: string,
profilePicture: string,
coverPicture: string,
coverColor: string,
bio: string,
pronouns: string,
note: string,
joinedDate: number,
lastLogin: number,
watchHours: number,
watchedAnimes: number,
activityStatus: string
}
}Error Responses:
400- Username not provided404- User not found
Initiate password recovery by finding the user.
/find-user-fpass| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
username |
string | Username of the account (case-insensitive). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/find-user-fpass", {
username: "animefan123",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
user: {
username: string,
profilePicture: string,
firstHalfEmail: string,
secondHalfEmail: string
},
accessToken: string
}Error Responses:
404- User not found500- Internal server error
Send OTP to user's registered email for password reset.
/verify-email-fpass| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
username |
string | Username (case-insensitive). | Yes | -- |
email |
string | Email address (must match registered email). | Yes | -- |
accessToken |
string | JWT access token from find-user-fpass endpoint. | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/verify-email-fpass", {
username: "animefan123",
email: "fan@example.com",
accessToken: "your_access_token",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
message: "OTP sent to your email"
}Error Responses:
400- Missing required fields403- Email does not match registered email404- User not found500- Failed to send OTP
Note: OTP is valid for 30 minutes after generation.
Verify the OTP sent to user's email.
/verify-otp-fpass| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
accessToken |
string | JWT access token from find-user-fpass endpoint. | Yes | -- |
otp |
string | 6-digit OTP sent to email (case-sensitive). | Yes | -- |
username |
string | Username (case-insensitive). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/verify-otp-fpass", {
accessToken: "your_access_token",
otp: "123456",
username: "animefan123",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
message: "OTP verified successfully"
}Error Responses:
400- Missing required fields, incorrect OTP, or OTP expired401- Unauthorized (token doesn't match user)500- Internal server error
Change user password after OTP verification.
/change-pass-fpass| Parameter | Type | Description | Required? | Default |
|---|---|---|---|---|
accessToken |
string | JWT access token from find-user-fpass endpoint. | Yes | -- |
username |
string | Username (case-insensitive). | Yes | -- |
newPassword |
string | New password (cannot be same as old password). | Yes | -- |
import axios from "axios";
const data = async () => {
try {
const { data } = await axios.post("/change-pass-fpass", {
accessToken: "your_access_token",
username: "animefan123",
newPassword: "newSecurePassword456",
});
return data;
} catch (err) {
throw new Error(err.message);
}
};
console.log(data);Success (200):
{
success: true,
message: "Password changed successfully"
}Error Responses:
400- Missing required fields, new password is same as old password, or password change cooldown not elapsed401- Unauthorized404- User not found500- Internal server error
Cooldown: Password can only be changed once every 15 days.
The API returns standard HTTP status codes:
200- Success400- Bad request (missing or invalid parameters)401- Unauthorized (authentication failed)403- Forbidden (insufficient permissions)404- Not found (resource doesn't exist)409- Conflict (duplicate username/email)500- Internal server error
All error responses include a message field with details about the error.
The API uses JWT tokens for authentication:
- Access Token - Short-lived token for API requests
- Refresh Token - Long-lived token for obtaining new access tokens
- Passwords are hashed using bcryptjs (bcrypt algorithm)
- Plaintext passwords are never stored
- Password changes are rate-limited to once every 15 days
- OTP-based email verification for password recovery
- OTP valid for 30 minutes
- 6-digit numeric OTP
CORS is configured via the ALLOWED_ORIGINS environment variable in .env.example. Specify a comma-separated list of allowed origins:
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://yourdomain.com
Only requests from these origins will be allowed.
Contributions are welcome!
Please read the CONTRIBUTING.md file for setup instructions, development guidelines, and contribution details.
If you like this project, consider giving it a star 🌟
Connect with me on X (Twitter): @subhajeetch
Join the Discord community: Animekun
This project is licensed under the MIT License - see the LICENSE file for more details.
