elysia icon indicating copy to clipboard operation
elysia copied to clipboard

Cloudflare Pages/Worker Example

Open verydanny opened this issue 1 year ago • 7 comments

The documentation briefly mentions Cloudflare, but Wrangler was a bit more annoying to set up than I expected. Should I add a documentation section or can I just somehow link to an example?

Regardless, I made an example.

Issues:

  • @elysiajs/html is incompatible with Cloudflare, using the underlying @kitajs/html seems to work, then again, no streaming sadly.
  • The bundle to deploy to Cloudflare seems to be quite big (170kb), vs Hono (27kb). Maybe someone can put more time in bundling Elysia for Cloudflare.

verydanny avatar Dec 21 '23 11:12 verydanny

Bit of a question but, did you manage to get environment variables working with Elysia?

tarosbubbletea avatar Dec 22 '23 20:12 tarosbubbletea

@tarosbubbletea

This is how I'm doing right now:

Tiny wrapper for cloudflare Elysia class:

export class Ely<
  BasePath extends string = "",
  Decorators extends DecoratorBase = {
    request: {};
    store: {};
    derive: {};
    resolve: {};
  },
  Definitions extends DefinitionBase = {
    type: {};
    error: {};
  },
  ParentSchema extends RouteSchema = {},
  Macro extends Record<string, unknown> = {},
  Routes extends RouteBase = {},
  Scoped extends boolean = false,
> extends Elysia<
  BasePath,
  Decorators & { request: { env: any; ctx: any } }, // <--- Notice this line: we are telling it that 'env' and 'ctx' are decorators
  Definitions,
  ParentSchema,
  Macro,
  Routes,
  Scoped
> {
  constructor(config?: Partial<ElysiaConfig<BasePath, Scoped>>) {
    config ??= {};
    config.aot = false;
    super(config);
  }
}

Then your app can access it:

const app = new Ely()
  .get("/", ({env, ctx}) => env.FOO) // Code completion and typescript works!

Finally declare the entry point like this:

export default {
  fetch(request: Request, env: any, ctx: any) {
    return new Ely().decorate({ env, ctx }).use(app).fetch(request);
  },
};

If you want to you can refine the env and ctx types to use your own Env interface and cloudflare's ExecutionContext.

eduardvercaemer avatar Jan 06 '24 01:01 eduardvercaemer

import { Elysia } from "elysia";
import type { Context } from "elysia";

type Env = {
  SECRET: string;
  API_HOST: string;
};

interface CF extends Context {
  env: Env;
  ctx: ExecutionContext;
}

const app = new Elysia({ aot: false });

app.get("/", (c: CF) => {
  console.log(typeof c.ctx.waitUntil);
  console.log(c.env.API_HOST);
  return "Elysia on Workers";
});

export default {
  fetch: (request: Request, env: Env, ctx: ExecutionContext) => {
    return app.decorate({ env, ctx }).handle(request);
  },
};

Rados51 avatar Mar 07 '24 12:03 Rados51

@Rados51 is there a way to use that context globally for multi file apis? (ie. have this in index.ts and want to use some properties in utils/auth.ts)

SelfhostedPro avatar Mar 21 '24 17:03 SelfhostedPro

@SelfhostedPro You can pass it as a function argument or store it as a state.

app.derive((c: CF) => {
  const auth = utilsAuth(c.ctx);
  return { auth }
});

Rados51 avatar Mar 21 '24 20:03 Rados51

Where does ExecutionContext come from?

magick93 avatar Jul 14 '24 21:07 magick93

Where does ExecutionContext come from?

@cloudflare/workers-types

Rados51 avatar Jul 14 '24 21:07 Rados51

This breaks type inference with things like params:

	app.get(
		'/foo/:bar',
		async (c: CF) => {
           return c.params.bar;
		},
		{
			params: t.Object({
				bar: t.String(),
			}),
		},
	);

Params is inferred as as never

Ehesp avatar Oct 18 '24 14:10 Ehesp

@Ehesp You just need to update to the latest version, and it will work out of the box.

Rados51 avatar Oct 18 '24 15:10 Rados51

@Ehesp in version like 0.8, you would use something like

interface CF extends Context<RouteSchema, DecoratorBase, "/id/:id"> {
  env: Env;
  ctx: ExecutionContext;
}
interface CF<P extends string> extends Context<RouteSchema, DecoratorBase, P> {
  env: Env;
  ctx: ExecutionContext;
}

Rados51 avatar Oct 18 '24 15:10 Rados51

@Rados51 Thanks, unless I'm doing something odd it doesn't seem to work on the latest (1.1.23):

import { Context, t } from 'elysia';

export interface App extends Context {
	env: Env;
	ctx: ExecutionContext;
}

sessionController.post('/:bar', (app: App) => {
  const requestBody = app.body;
  console.log(app.params);

  // ...
}, {
	body: schemaSessionCreate,
    params: t.Object({
      bar: t.String(),
    }),
});

In this case, app.ctx and app.env are typed, however app.body is unknown, and app.params is a generic Record<string, string>.

Ehesp avatar Oct 24 '24 10:10 Ehesp