base backend setup
This commit is contained in:
parent
a83c05aa01
commit
debe0f13a2
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
@ -7,12 +7,16 @@
|
||||
"start": "bun run src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bogeychan/elysia-etag": "^0.0.6",
|
||||
"@elysiajs/cors": "^1.1.1",
|
||||
"@elysiajs/swagger": "^1.1.3",
|
||||
"@ssr/common": "workspace:common",
|
||||
"@tqman/nice-logger": "^1.0.1",
|
||||
"elysia": "latest",
|
||||
"elysia-autoroutes": "^0.5.0",
|
||||
"elysia-decorators": "^1.0.2"
|
||||
"elysia-decorators": "^1.0.2",
|
||||
"elysia-helmet": "^2.0.0",
|
||||
"elysia-rate-limit": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "latest"
|
||||
|
75
projects/backend/src/common/http-codes.ts
Normal file
75
projects/backend/src/common/http-codes.ts
Normal file
@ -0,0 +1,75 @@
|
||||
export const HttpCode = {
|
||||
// 1xx Informational
|
||||
CONTINUE: { code: 100, message: "Continue" },
|
||||
SWITCHING_PROTOCOLS: { code: 101, message: "Switching Protocols" },
|
||||
PROCESSING: { code: 102, message: "Processing" },
|
||||
EARLY_HINTS: { code: 103, message: "Early Hints" },
|
||||
|
||||
// 2xx Success
|
||||
OK: { code: 200, message: "OK" },
|
||||
CREATED: { code: 201, message: "Created" },
|
||||
ACCEPTED: { code: 202, message: "Accepted" },
|
||||
NON_AUTHORITATIVE_INFORMATION: { code: 203, message: "Non-Authoritative Information" },
|
||||
NO_CONTENT: { code: 204, message: "No Content" },
|
||||
RESET_CONTENT: { code: 205, message: "Reset Content" },
|
||||
PARTIAL_CONTENT: { code: 206, message: "Partial Content" },
|
||||
MULTI_STATUS: { code: 207, message: "Multi-Status" },
|
||||
ALREADY_REPORTED: { code: 208, message: "Already Reported" },
|
||||
IM_USED: { code: 226, message: "IM Used" },
|
||||
|
||||
// 3xx Redirection
|
||||
MULTIPLE_CHOICES: { code: 300, message: "Multiple Choices" },
|
||||
MOVED_PERMANENTLY: { code: 301, message: "Moved Permanently" },
|
||||
FOUND: { code: 302, message: "Found" },
|
||||
SEE_OTHER: { code: 303, message: "See Other" },
|
||||
NOT_MODIFIED: { code: 304, message: "Not Modified" },
|
||||
USE_PROXY: { code: 305, message: "Use Proxy" },
|
||||
TEMPORARY_REDIRECT: { code: 307, message: "Temporary Redirect" },
|
||||
PERMANENT_REDIRECT: { code: 308, message: "Permanent Redirect" },
|
||||
|
||||
// 4xx Client Errors
|
||||
BAD_REQUEST: { code: 400, message: "Bad Request" },
|
||||
UNAUTHORIZED: { code: 401, message: "Unauthorized" },
|
||||
PAYMENT_REQUIRED: { code: 402, message: "Payment Required" },
|
||||
FORBIDDEN: { code: 403, message: "Forbidden" },
|
||||
NOT_FOUND: { code: 404, message: "Not Found" },
|
||||
METHOD_NOT_ALLOWED: { code: 405, message: "Method Not Allowed" },
|
||||
NOT_ACCEPTABLE: { code: 406, message: "Not Acceptable" },
|
||||
PROXY_AUTHENTICATION_REQUIRED: { code: 407, message: "Proxy Authentication Required" },
|
||||
REQUEST_TIMEOUT: { code: 408, message: "Request Timeout" },
|
||||
CONFLICT: { code: 409, message: "Conflict" },
|
||||
GONE: { code: 410, message: "Gone" },
|
||||
LENGTH_REQUIRED: { code: 411, message: "Length Required" },
|
||||
PRECONDITION_FAILED: { code: 412, message: "Precondition Failed" },
|
||||
PAYLOAD_TOO_LARGE: { code: 413, message: "Payload Too Large" },
|
||||
URI_TOO_LONG: { code: 414, message: "URI Too Long" },
|
||||
UNSUPPORTED_MEDIA_TYPE: { code: 415, message: "Unsupported Media Type" },
|
||||
RANGE_NOT_SATISFIABLE: { code: 416, message: "Range Not Satisfiable" },
|
||||
EXPECTATION_FAILED: { code: 417, message: "Expectation Failed" },
|
||||
IM_A_TEAPOT: { code: 418, message: "I'm a teapot" },
|
||||
MISDIRECTED_REQUEST: { code: 421, message: "Misdirected Request" },
|
||||
UNPROCESSABLE_ENTITY: { code: 422, message: "Unprocessable Entity" },
|
||||
LOCKED: { code: 423, message: "Locked" },
|
||||
FAILED_DEPENDENCY: { code: 424, message: "Failed Dependency" },
|
||||
TOO_EARLY: { code: 425, message: "Too Early" },
|
||||
UPGRADE_REQUIRED: { code: 426, message: "Upgrade Required" },
|
||||
PRECONDITION_REQUIRED: { code: 428, message: "Precondition Required" },
|
||||
TOO_MANY_REQUESTS: { code: 429, message: "Too Many Requests" },
|
||||
REQUEST_HEADER_FIELDS_TOO_LARGE: { code: 431, message: "Request Header Fields Too Large" },
|
||||
UNAVAILABLE_FOR_LEGAL_REASONS: { code: 451, message: "Unavailable For Legal Reasons" },
|
||||
|
||||
// 5xx Server Errors
|
||||
INTERNAL_SERVER_ERROR: { code: 500, message: "Internal Server Error" },
|
||||
NOT_IMPLEMENTED: { code: 501, message: "Not Implemented" },
|
||||
BAD_GATEWAY: { code: 502, message: "Bad Gateway" },
|
||||
SERVICE_UNAVAILABLE: { code: 503, message: "Service Unavailable" },
|
||||
GATEWAY_TIMEOUT: { code: 504, message: "Gateway Timeout" },
|
||||
HTTP_VERSION_NOT_SUPPORTED: { code: 505, message: "HTTP Version Not Supported" },
|
||||
VARIANT_ALSO_NEGOTIATES: { code: 506, message: "Variant Also Negotiates" },
|
||||
INSUFFICIENT_STORAGE: { code: 507, message: "Insufficient Storage" },
|
||||
LOOP_DETECTED: { code: 508, message: "Loop Detected" },
|
||||
NOT_EXTENDED: { code: 510, message: "Not Extended" },
|
||||
NETWORK_AUTHENTICATION_REQUIRED: { code: 511, message: "Network Authentication Required" },
|
||||
} as const;
|
||||
|
||||
export type HttpCode = typeof HttpCode[keyof typeof HttpCode];
|
@ -1,9 +1,9 @@
|
||||
import { Controller, Get } from "elysia-decorators";
|
||||
import { getAppVersion } from "../common/app-utils";
|
||||
|
||||
@Controller("/")
|
||||
@Controller()
|
||||
export default class AppController {
|
||||
@Get()
|
||||
@Get("/")
|
||||
public index() {
|
||||
return {
|
||||
app: "backend",
|
11
projects/backend/src/error/rate-limit-error.ts
Normal file
11
projects/backend/src/error/rate-limit-error.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { HttpCode } from "../common/http-codes";
|
||||
|
||||
export class RateLimitError extends Error {
|
||||
constructor(
|
||||
public message: string = 'rate-limited',
|
||||
public detail: string = '',
|
||||
public status: number = HttpCode.TOO_MANY_REQUESTS.code
|
||||
) {
|
||||
super(message)
|
||||
}
|
||||
}
|
@ -2,7 +2,12 @@ import { Elysia } from "elysia";
|
||||
import cors from "@elysiajs/cors";
|
||||
import { decorators } from "elysia-decorators";
|
||||
import { logger } from "@tqman/nice-logger";
|
||||
import AppController from "./controller/app";
|
||||
import { swagger } from '@elysiajs/swagger'
|
||||
import { rateLimit } from 'elysia-rate-limit'
|
||||
import { RateLimitError } from "./error/rate-limit-error";
|
||||
import { helmet } from 'elysia-helmet';
|
||||
import { etag } from '@bogeychan/elysia-etag'
|
||||
import AppController from "./controller/app.controller";
|
||||
|
||||
const app = new Elysia();
|
||||
|
||||
@ -23,6 +28,11 @@ app.onError({ as: "global" }, ({ code, error }) => {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Enable E-Tags
|
||||
*/
|
||||
app.use(etag());
|
||||
|
||||
/**
|
||||
* Enable CORS
|
||||
*/
|
||||
@ -37,6 +47,30 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Rate limit (100 requests per minute)
|
||||
*/
|
||||
app.use(rateLimit({
|
||||
scoping: "global",
|
||||
duration: 60 * 1000,
|
||||
max: 100,
|
||||
skip: (request) => {
|
||||
let [ _, path ] = request.url.split("/"); // Get the url parts
|
||||
path === "" || path === undefined && (path = "/"); // If we're on /, the path is undefined, so we set it to /
|
||||
return path === "/"; // ignore all requests to /
|
||||
},
|
||||
errorResponse: new RateLimitError("Too many requests, please try again later"),
|
||||
}))
|
||||
|
||||
/**
|
||||
* Security settings
|
||||
*/
|
||||
app.use(helmet({
|
||||
hsts: false, // Disable HSTS
|
||||
contentSecurityPolicy: false, // Disable CSP
|
||||
dnsPrefetchControl: true, // Enable DNS prefetch
|
||||
}))
|
||||
|
||||
/**
|
||||
* Controllers
|
||||
*/
|
||||
@ -46,6 +80,11 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Swagger Documentation
|
||||
*/
|
||||
app.use(swagger());
|
||||
|
||||
app.onStart(() => {
|
||||
console.log("Listening on port http://localhost:8080");
|
||||
});
|
||||
|
Reference in New Issue
Block a user