{"id":21575332,"url":"https://github.com/patrick11514/sveltekitapi","last_synced_at":"2025-03-18T06:45:22.250Z","repository":{"id":221908516,"uuid":"754855353","full_name":"patrick11514/SveltekitAPI","owner":"patrick11514","description":null,"archived":false,"fork":false,"pushed_at":"2025-01-26T10:32:10.000Z","size":7377,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-26T12:38:58.031Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patrick11514.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"publiccode":null,"codemeta":null}},"created_at":"2024-02-08T22:18:01.000Z","updated_at":"2025-01-26T10:32:13.000Z","dependencies_parsed_at":"2024-02-11T01:15:10.066Z","dependency_job_id":"8ba40710-1011-4f19-8d74-7a97d182d879","html_url":"https://github.com/patrick11514/SveltekitAPI","commit_stats":null,"previous_names":["patrick11514/sveltekitapihandler"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick11514%2FSveltekitAPI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick11514%2FSveltekitAPI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick11514%2FSveltekitAPI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patrick11514%2FSveltekitAPI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patrick11514","download_url":"https://codeload.github.com/patrick11514/SveltekitAPI/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244173506,"owners_count":20410295,"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":[],"created_at":"2024-11-24T12:12:54.166Z","updated_at":"2025-03-18T06:45:22.230Z","avatar_url":"https://github.com/patrick11514.png","language":"TypeScript","readme":"# Sveltekit API\n\nPackage for creating [SvelteKit](https://kit.svelte.dev/) API endpoints with typesafe routes and client.\n\n_This package is highly inspired by [TRPC](https://trpc.io)'s structure._\n\n## Showcase\n\n![Showcase](./assets/showcase.gif)\n\n-   First step is creating new API with your context, which will be accesible in every procedure and middleware.\n    Also you can export router and basic procedure.\n\n    **src/lib/server/api.ts**\n\n    ```TS\n\n    import { APICreate } from '@patrick115/sveltekitapi'\n    import type { Context } from './context'\n\n    export const api = new APICreate\u003cContext\u003e()\n\n    export const router = api.router\n    export const procedure = api.procedure\n\n    ```\n\n-   Here you can create your context, which get called on every request and get passed SvelteKit's RequestEvent.\n\n    **src/lib/server/context.ts**\n\n    ```TS\n    import type { AsyncReturnType, CreateContext } from '@patrick115/sveltekitapi'\n\n    export const context = (async (ev /*\u003c- SvelteKit's RequestEvent */) =\u003e {\n        return {} // Here you can put your context\n    }) satisfies CreateContext\n\n    export type Context = AsyncReturnType\u003ctypeof context\u003e\n    ```\n\n-   Now we create router and pass object to it with our procedures. In each procedure we can specify HTTP method (GET, POST, PUT, DELETE, PATCH). For methods other than GET we can specify input schema with .input(ZodSchema). Then we specify what to do with the request with .query(). Parameters for query function are: context, input (in case of method other than GET), and ev, which is RequestEvent from SvelteKit in case you need to set cookies, or get user's ip or access the raw request.\n\n    **src/lib/server/routes.ts**\n\n    ```TS\n    import { json } from '@sveltejs/kit'\n    import { z } from 'zod'\n    import { postProcedure, proc2, procedure, router } from './api'\n\n    export const r = router({\n        example: procedure.GET.query(() =\u003e {\n            return 'Hello from the API!'\n        }),\n    })\n\n    export type AppRouter = typeof r\n    ```\n\n-   At the end we create server and pass in the router, path to API and context.\n\n    **src/lib/server/server.ts**\n\n    ```TS\n    import { APIServer } from '@patrick115/sveltekitapi'\n    import { context } from './context'\n    import { r } from './routes'\n\n    export const Server = new APIServer({\n        router: r,\n        path: '/api',\n        context\n    })\n    ```\n\n-   If we want to use our API in SvelteKit's endpoint we can do it like this:\n    (export const for each method you want to use, in this case GET, POST, PUT, DELETE, PATCH)\n\n    **src/routes/api/[...data]/+server.ts**\n\n    ```TS\n    import { Server } from '$/lib/server/server'\n\n    export const GET = Server.handler\n    export const POST = Server.handler\n    export const PUT = Server.handler\n    export const DELETE = Server.handler\n    export const PATCH = Server.handler\n\n    ```\n\n-   Now syncing with frontend\n-   First we create an API client. As type we pass our router type and as parameter we pass rootPath for our API (same as in server).\n\n    **src/lib/api.ts**\n\n    ```TS\n    import { createAPIClient } from '@patrick115/sveltekitapi'\n    import type { AppRouter } from './server/routes'\n\n    export const API = createAPIClient\u003cAppRouter\u003e('/api')\n\n    ```\n\n-   Syncing with frontend. From load function we return object with our object returned from Server.hydrateToClient() function.\n\n    **src/routes/+layout.server.ts**\n\n    ```TS\n    import { Server } from '$/lib/server/server'\n    import type { LayoutServerLoad } from './$types'\n\n    export const load = (async () =\u003e {\n        return {\n            api: Server.hydrateToClient()\n        }\n    }) satisfies LayoutServerLoad\n    ```\n\n-   Now we need to pass this object to our client\n\n    **src/routes/+layout.svelte**\n\n    ```html\n    \u003cscript lang=\"ts\"\u003e\n        import { API } from '$/lib/api';\n        import type { Snippet } from 'svelte';\n        import type { LayoutData } from './$types';\n\n        let { children, data }: { children: Snippet; data: LayoutData } = $props();\n\n        API.hydrateFromServer(data.api);\n    \u003c/script\u003e\n\n    {@render children()}\n    ```\n\n-   Now we can call our API from our frontend\n\n    **src/routes/+page.svelte**\n\n    ```html\n    \u003cscript lang=\"ts\"\u003e\n        import { API } from '$/lib/api';\n        import { onMount } from 'svelte';\n\n        onMount(async () =\u003e {\n            const res = await API.example();\n            console.log(res);\n        });\n    \u003c/script\u003e\n\n    \u003ch1\u003eHello from SvelteKit!\u003c/h1\u003e\n    ```\n\n## Installation\n\n```bash\n#npm\nnpm install @patrick115/sveltekitapi\n\n#pnpm\npnpm install @patrick115/sveltekitapi\n\n#yarn\nyarn add @patrick115/sveltekitapi\n```\n\n## Usage\n\n### Context\n\nContext is a function that gets called on every request and returns object with data that will be accesible in every procedure and middleware. It gets passed SvelteKit's RequestEvent.\n\nExample of passing user's IP and session cookie to every procedure and middleware.\n\n```TS\nimport type { AsyncReturnType, CreateContext } from '@patrick115/sveltekitapi'\n\nexport const context = (async (ev) =\u003e {\n    const ip = ev.getClientAddress()\n    const cookie = ev.cookies.get(\"session\")\n    return {\n        cookie,\n        ip\n    }\n}) satisfies CreateContext\n\nexport type Context = AsyncReturnType\u003ctypeof context\u003e\n```\n\n### Middleware\n\nMiddleware is a function that gets called before every request on procedure, that uses that middleware. It gets passed context, input (with unknown type, because it can be used on multiple endpoints with multiple methods. In case of GET method, input contains undefined), SvelteKit's RequestEvent and next function, which is used to call next middleware or procedure. You need to call this function at the end of your middleware and return its result. You can pass new context as next function's parameter.\n\nExample of middleware that checks if user is logged in and if not, it returns error.\n\n```TS\nimport { MiddleWareError } from '@patrick115/sveltekitapi'\n\nexport const procedure = api.procedure\n\nexport const securedProcedure = procedure.use(async ({ctx, next}) =\u003e {\n    if (!ctx.cookie) {\n        throw new MiddleWareError({\n            status: 401,\n            message: 'You need to be logged in to access this endpoint.'\n        })\n    }\n\n    const data = jwt.getCookie\u003cUser\u003e(ctx.cookie)\n\n    if (!data) {\n        throw new MiddleWareError({\n            status: 401,\n            message: 'You need to be logged in to access this endpoint.'\n        })\n    }\n\n    return next({\n        ...ctx, //note, that context will be overwritten with new context, so if you want to pass some data from old context, you need to pass it here\n        user: data\n    })\n})\n```\n\n### Procedure\n\nIn router we can define procedures, each procedure can have each HTTP method (GET, POST, PUT, DELETE, PATCH). For methods other than GET we can specify input schema with .input(ZodSchema). Then we specify what to do with the request with .query(). Parameters for query function are: context, input (in case of method other than GET), and ev, which is RequestEvent from SvelteKit in case you need to set cookies, or get user's ip or access the raw request.\n\nNote: if some procedure implements some middleware, return type will be ErrorApiResponse | your returned type, since you can throw error from middleware.\n\nExample of procedure that returns Hello World.\n\n```TS\n\nimport { procedure, router } from './api'\n\nexport const r = router({\n    example: procedure.GET.query(() =\u003e {\n        return `Hello world` as const\n    })\n})\n\nexport type AppRouter = typeof r\n```\n\nCalling this procedure from frontend.\n\n```TS\nconst data = await API.example()\nconsole.log(data) //Hello world\n//           ^? data: \"Hello world\"\n//Note, if this procedure would implement some middleware, return type would be ErrorApiResponse | \"Hello world\"\n```\n\nMultiple HTTP methods on one endpoint.\n\n```TS\nimport { z } from 'zod'\nimport { procedure, router } from './api'\n\nexport const r = router({\n    example: [\n        procedure.GET.query(() =\u003e {\n            return `Hello world` as const\n        }),\n        procedure.POST.input(\n            z.object({\n                username: z.string()\n            })\n        ).query(({ input }) =\u003e {\n            return `Hello ${input.username}` as const\n        })\n    ]\n})\n\nexport type AppRouter = typeof r\n```\n\nCalling this procedure from frontend.\n\n```TS\nconst data = await API.example.GET() //here we can see, that we need to select which method we want to call\nconsole.log(data)\n//           ^? data: \"Hello world\"\n\nconst data2 = await API.example.POST({\n    username: 'Patrik'\n})\nconsole.log(data2)\n//           ^? data: \"Hello ${string}\"\n```\n\nProcedure with FormData as input\n\n```TS\nimport { FormDataInput } from '@patrick115/sveltekitapi'\nimport { procedure, router } from './api'\n\nexport const r = router({\n    example: procedure.POST.input(FormDataInput).query(({ input }) =\u003e {\n        const name = input.get('name')\n        return `Hello ${name ?? 'World'}` as const\n    })\n})\n\nexport type AppRouter = typeof r\n\n```\n\nCalling this procedure from frontend.\n\n```TS\nconst formData = new FormData()\nformData.append(\"name\", \"Patrik)\n\nconst data = await API.example(formData)\nconsole.log(data) //Hello Patrik\n//           ^? data: \"Hello ${string}\"\n```\n\nExtending endpoint with sub routes\n\n```TS\nimport { z } from 'zod'\nimport { procedure, router } from './api'\n\nexport const r = router({\n    example: [\n        procedure.GET.query(() =\u003e {\n            return `Hello world` as const\n        }),\n        procedure.POST.input(\n            z.object({\n                username: z.string()\n            })\n        ).query(({ input }) =\u003e {\n            return `Hello ${input.username}` as const\n        }),\n        //Subroutes, but only single sub-object is supported\n        {\n            // /api/example/hello\n            hello: procedure.GET.query(() =\u003e {\n                return \"Hello World, again\" as const\n            })\n        }\n    ]\n})\n\nexport type AppRouter = typeof r\n```\n\nCalling this procedure from frontend.\n\n```TS\nconst data = await API.example.GET() //here we can see, that we need to select which method we want to call\nconsole.log(data)\n//           ^? data: \"Hello world\"\n\nconst data2 = await API.example.POST({\n    username: 'Patrik'\n})\nconsole.log(data2)\n//           ^? data: \"Hello ${string}\"\n\nconst data3 = await API.example.hello()\nconsole.log(data3)\n//           ^? data: \"Hello World, again\"\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrick11514%2Fsveltekitapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatrick11514%2Fsveltekitapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatrick11514%2Fsveltekitapi/lists"}