Function
Source:
src/Railway/Function.ts
A Railway.Function is a single TypeScript file on Railway’s Bun
canvas runtime. No GitHub repo. No Docker. No registry. Alchemy
queries functionRuntime(bun), creates the Service with that image,
and writes the source as startCommand (./run.sh + base64).
There are two ways to define a Function. Prefer async (main +
export default { fetch }, no Effect) — Effect-native canvas
Functions pull the Effect runtime into the 96KB encoded start-command
cap and fail FunctionTooLarge for anything non-trivial. Use
Service when you need an Effect program or a real image.
Distinct from Effect-native Service (main), which bundles
with Rolldown and uploads a generated Dockerfile for Railway to
build.
Async Functions
Section titled “Async Functions”You don’t have to use Effect. Declare the Function with main
pointing at a file and no Effect.gen implementation — Alchemy
bundles that file as-is (no Effect runtime) and deploys it as a
canvas Function. The handler is a plain async fetch. Use the env
prop to declare variables, and InferEnv to type the second
argument (Railway bindings are environment variables).
Defining an async Function in your stack
const db = yield* Railway.Postgres("Db", { project: site });
export const Ping = Railway.Function("Ping", { project: site, main: "./src/ping.ts", env: { DATABASE_URL: db.connectionUri },});
export type PingEnv = Railway.InferEnv<typeof Ping>;Writing the async handler
import type { PingEnv } from "../alchemy.run.ts";
export default { async fetch(_request: Request, env: PingEnv) { return new Response(env.DATABASE_URL ? "ok" : "missing"); },};Effect-native Function
Section titled “Effect-native Function”A Function is a class. Props describe the canvas Function. The Effect
is the program that runs in it. main: import.meta.url is the bundle
entrypoint — Alchemy bundles this file into one JS file and deploys
it. No registry. Stay tiny: the encoded start command maxes out at
96KB (effect plus handlers overflows it).
export default class Ping extends Railway.Function<Ping>()( "Ping", { project: Site, main: import.meta.url, }, Effect.gen(function* () { return { fetch: Effect.succeed(HttpServerResponse.text("ok")), }; }),) {}Inline source
Section titled “Inline source”Pass inline source (or path to a .ts file) instead of main
when the Function is not an Effect class. Alchemy generates a
*.up.railway.app domain unless http: false or a cron schedule is
set.
const site = yield* Railway.Project("Site");const ping = yield* Railway.Function("Ping", { project: site, source: ` Bun.serve({ hostname: "0.0.0.0", port: Number(process.env.PORT ?? 3000), fetch() { return new Response("ok"); }, }); `,});File path
Section titled “File path”path is the CLI --path equivalent. The file is read at
plan/reconcile. Content changes update in place.
const ping = yield* Railway.Function("Ping", { project: site, path: "./fn.ts",});cronSchedule runs the Function on a cron expression (--cron).
HTTP domains are skipped unless http: true is set explicitly.
const job = yield* Railway.Function("Cleanup", { project: site, source: `console.log("tick");`, cronSchedule: "0 * * * *",});Serverless
Section titled “Serverless”sleepApplication sleeps the Function when idle (--serverless).
const ping = yield* Railway.Function("Ping", { project: site, source: `console.log("hi");`, http: false, sleepApplication: true,});Schemaless RPC
Section titled “Schemaless RPC”Return methods next to fetch. Call enableRailwayRpc() in init
(canvas Functions are capped at 96KB; the dispatcher is opt-in).
Another Function or Service binds this class and calls them over
{name}.railway.internal with a shared token. Public
*.up.railway.app requests to /__rpc__/* get 401.
export default class Query extends Railway.Function<Query>()( "Query", { project: Site, main: import.meta.url }, Effect.gen(function* () { Railway.enableRailwayRpc(); return { greet: (name: string) => Effect.succeed(`hello ${name}`), }; }),) {}
export default class Api extends Railway.Service<Api>()( "Api", { project: Site, main: import.meta.url }, Effect.gen(function* () { const query = yield* Railway.bindFunction(Query); return { fetch: query .greet("sam") .pipe(Effect.map((greeting) => HttpServerResponse.text(greeting))), }; }),) {}Module-scope declarations
Section titled “Module-scope declarations”Resource-valued props accept the resource or an Effect producing it.
import * as Railway from "alchemy/Railway";
export const Site = Railway.Project("Site");export const Ping = Railway.Function("Ping", { project: Site, source: `console.log("hello");`, http: false,});