Skip to content

Instantly share code, notes, and snippets.

@staciax
Last active June 10, 2025 15:08
Show Gist options
  • Save staciax/f764730328cc1105d2a388243dc5995c to your computer and use it in GitHub Desktop.
Save staciax/f764730328cc1105d2a388243dc5995c to your computer and use it in GitHub Desktop.
ElysiaJS with background tasks
/**
* Background Tasks Implementation
* Inspired by Starlette's background task processing
* @see https://github.com/encode/starlette/blob/master/starlette/background.py
*/
import { Elysia } from 'elysia';
export interface IBackgroundTask {
run(): Promise<void>;
}
const isAsyncFunction = <P extends any[]>(
func: (...args: P[]) => any,
): boolean => func.constructor.name === 'AsyncFunction';
type TaskFunction<P extends any[]> = (...args: P) => void | Promise<void>;
export class BackgroundTask<P extends any[]> implements IBackgroundTask {
public readonly func: TaskFunction<P>;
public readonly args: P;
public readonly isAsync: boolean;
constructor(func: TaskFunction<P>, ...args: P) {
this.func = func;
this.args = args;
this.isAsync = isAsyncFunction(func);
}
async run(): Promise<void> {
if (this.isAsync) {
await this.func(...this.args);
} else {
throw new Error(
'Background task does not support synchronous functions. Please use async functions.',
);
// NOTE: I'm not sure how to run a synchronous function in the background
// without blocking the main thread. I tried using The Worker API but it
// doesn't seem to work with synchronous functions.
// Reference: https://bun.sh/docs/api/workers
}
}
}
export class BackgroundTasks implements IBackgroundTask {
public readonly tasks: BackgroundTask<any[]>[];
constructor(tasks: BackgroundTask<any[]>[] = []) {
this.tasks = tasks;
// this.tasks = [...tasks]; // clone the array to avoid mutation
}
addTask<P extends any[]>(func: TaskFunction<P>, ...args: P): void {
const task = new BackgroundTask(func, ...args);
this.tasks.push(task);
}
async run(): Promise<void> {
// If you want to run all tasks concurrently, you can use Promise.all
// await Promise.all(this.tasks.map((task) => task.run()));
// Otherwise, run them sequentially
for (const task of this.tasks) {
await task.run();
}
}
}
export const backgroundTasksPlugin = new Elysia({ name: 'background-tasks' })
.derive(() => ({
backgroundTasks: new BackgroundTasks(),
}))
.onAfterResponse(({ backgroundTasks }) => {
backgroundTasks.run().catch((e) => {
const error = e instanceof Error ? e.stack : e;
const now = new Date().toISOString();
console.error(`background-tasks: ${now} - ${error}`);
});
})
.as('scoped');
import { Elysia } from 'elysia';
import { backgroundTasksPlugin } from './background';
async function test_async_task() {
await new Promise((resolve) => setTimeout(resolve, 3000)); // Simulate a 3-second async task
console.log('Async task completed');
}
const app = new Elysia()
.use(backgroundTasksPlugin)
.get('/', ({ backgroundTasks }) => {
backgroundTasks.addTask(test_async_task);
backgroundTasks.addTask(test_async_task);
return 'task initiated';
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
@staciax
Copy link
Author

staciax commented Jun 10, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment