{"id":16419062,"url":"https://github.com/neo-ciber94/next-server-task","last_synced_at":"2025-10-26T20:31:39.284Z","repository":{"id":200386449,"uuid":"705394895","full_name":"Neo-Ciber94/next-server-task","owner":"Neo-Ciber94","description":"Provides a mechanism for executing long running tasks on NextJS edge api-handlers","archived":false,"fork":false,"pushed_at":"2024-02-17T01:50:10.000Z","size":12297,"stargazers_count":37,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T00:13:33.172Z","etag":null,"topics":["api-handler","edge-runtime","nextjs","server-sent-events","typescript","vercel"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Neo-Ciber94.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-10-15T22:21:52.000Z","updated_at":"2024-12-09T08:23:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"1db36722-929f-4142-ae5a-5db4047e33d3","html_url":"https://github.com/Neo-Ciber94/next-server-task","commit_stats":null,"previous_names":["neo-ciber94/next-server-task"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neo-Ciber94%2Fnext-server-task","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neo-Ciber94%2Fnext-server-task/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neo-Ciber94%2Fnext-server-task/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Neo-Ciber94%2Fnext-server-task/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Neo-Ciber94","download_url":"https://codeload.github.com/Neo-Ciber94/next-server-task/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238397149,"owners_count":19465124,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-handler","edge-runtime","nextjs","server-sent-events","typescript","vercel"],"created_at":"2024-10-11T07:15:48.144Z","updated_at":"2025-10-26T20:31:33.886Z","avatar_url":"https://github.com/Neo-Ciber94.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# next-server-task\n\n[![CI](https://github.com/Neo-Ciber94/next-server-task/actions/workflows/ci.yml/badge.svg)](https://github.com/Neo-Ciber94/next-server-task/actions/workflows/ci.yml)\n\nExecute long running tasks on `NextJS` edge API handlers.\n\nYou can also checkout this [Example](https://github.com/Neo-Ciber94/next-server-task/tree/main/examples/openai-image-generator).\n\n## Table of contents\n\n1. [Install](#install)\n2. [How it works?](#how-it-works)\n3. [Usage example](#usage-example)\n4. [Accessing the request with TaskServerContext](#accessing-the-request-with-taskservercontext)\n5. [TaskError](#taskerror)\n6. [License](#license)\n\n## Install\n\n```bash\nnpm install next-server-task\n```\n\n```bash\nyarn add next-server-task\n```\n\n```bash\npnpm add next-server-task\n```\n\n## How it works?\n\n```mermaid\nsequenceDiagram\n    participant Client\n    participant Server\n\n    Client-\u003e\u003eServer: Make a request\n    Server-\u003e\u003eClient: Establish SSE connection\n\n    loop Processing\n        Server--\u003e\u003eClient: Send \"wait\" events (while processing)\n        Server--xClient: An error occurred\n        Server-\u003e\u003eClient: Send \"server-error\" or \"internal-error\" event\n        Server--\u003e\u003eClient: Send \"settle\" event (processing finished)\n    end\n```\n\nWe can keep the connection alive thanks we use [Server Sent Events](https://web.dev/articles/eventsource-basics), while the\ntask is running we sent a `wait` event each 300ms (this can be changed) to notify we still processing, if not error happened we send a `settle` event with the data, if an error ocurred we send an `internal-error` if the error was unexpected or a `server-error` of the error was throw using `TaskError`, these errors are rethrow on the client and the connection is closed.\n\n## Usage example\n\nIn this example we use the **OpenAI** to generate images which can take a long time to generate the images,\nthis usually led to timeouts when using platforms like vercel, but using `next-server-task` we can wait until the task\nfinish and send the result after that.\n\nOn the server:\n\n```ts\n// app/api/generate-image/route.ts\n\nimport { TaskError } from \"next-server-task\";\nimport { createTask } from \"next-server-task/server\";\nimport { OpenAI } from \"openai\";\n\nexport const runtime = \"edge\";\n\nconst generateImage = createTask(\"/api/generate-image\").withAction(\n  async ({ prompt }: { prompt: string }) =\u003e {\n    const openAPI = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n    const results = await openAPI.images.generate({ prompt });\n\n    const url = results.data[0].url;\n\n    if (url == null) {\n      throw new TaskError(\"Failed to generate image\");\n    }\n\n    return { url };\n  }\n);\n\nexport type GenerateImage = typeof generateImage;\n\nconst { handler } = generateImage.serverHandler();\nexport { handler as GET };\n```\n\nOn the client\n\n```tsx\n// ImageGenerator.tsx\n\nimport React, { useState } from \"react\";\nimport Image from \"next/image\";\nimport { type GenerateImage } from \"./api/generate-image/route\";\nimport { createClient } from \"next-server-task/client\";\n\nconst client = createClient\u003cGenerateImage\u003e();\n\nexport default function ImageGenerator() {\n    const [imageUrl, setImageUrl] = useState\u003cstring\u003e();\n    const [error, setError] = useState\u003cstring\u003e();\n    const { mutate, isMutating } = client.useTask(\"/api/generate-image\");\n\n    const handleSubmit = async (e: React.FormEvent\u003cHTMLFormElement\u003e) =\u003e {\n        e.preventDefault();\n\n        setError(undefined);\n        const form = new FormData(e.currentTarget);\n        const prompt = form.get(\"prompt\")?.toString() ?? \"\";\n        \n        try {\n            const result = await mutate({ prompt });\n            setImageUrl(result.url);\n        }\n        catch (err: any) {\n            const message = err?.message ?? \"Failed to generate image\";\n            setError(message);\n        }\n    };\n\n    return \u003cdiv\u003e\n        {imageUrl \u0026\u0026 \n            \u003cImage \n                alt={\"Generated Image\"} \n                src={imageUrl} \n                width={256} \n                height={256}\n            /\u003e}\n\n        \u003cform onSubmit={handleSubmit}\u003e\n            \u003cinput placeholder=\"Prompt...\" name=\"prompt\"/\u003e\n            \u003cbutton type=\"submit\"\u003eGenerate\u003c/button\u003e\n        \u003c/form\u003e\n\n        {isMutating \u0026\u0026 \u003cp\u003eLoading...\u003c/p\u003e}\n        {error \u0026\u0026 \u003cp style={{ color: \"red\" }}\u003e{error}\u003c/p\u003e}\n    \u003c/div\u003e\n}\n```\n\n## Accessing the request with TaskServerContext\n\nYou can access the request in the task using the `TaskServerContext`.\n\nThe `TaskServerContext` had this shape:\n\n```ts\ntype TaskServerContext = {\n    req: Request,\n    params: Record\u003cstring, string | undefined\u003e\n}\n```\n\n```ts\n// server\nconst myTask = createTask(\"/api/my-task\").withAction((_, ctx) =\u003e {\n    const url = ctx.req.url;\n    return { url };\n})\n```\n\n## TaskError\n\nYou can throw expected errors using `TaskError`, this errors are rethrow on the client side as a `TaskClientError` so can be handled in a `try-catch` block.\n\n```ts\n// server\nconst myTask = createTask(\"/api/my-task\").withAction(() =\u003e {\n    const randomNumber = Math.random();\n    if (randomNumber \u003e 0.5) {\n        throw new TaskError(\"Invalid number\");\n    }\n\n    return { randomNumber };\n})\n```\n\n```ts\n// client\nconst { mutate, isMutating} = useTask(\"/api/my-task\");\n\ntry {\n    const { randomNumber } = mutate();\n    console.log(randomNumber);\n}\ncatch (err) {\n    if (err instanceof TaskClientError) {\n        console.log(err.message, err.code);\n    }\n}\n```\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneo-ciber94%2Fnext-server-task","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneo-ciber94%2Fnext-server-task","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneo-ciber94%2Fnext-server-task/lists"}