Skip to main content
MigrationVal Town

Migrate Val Town code

Turn your vals into a Node.js project and deploy it to an Easel preview.

Move each app's vals into a Git repository as an ordinary Node.js project, then deploy it to a preview. Data and other services come later, in their own guides.

Start with the app

Read the vals before changing them. List each HTTP route and the methods it accepts, each cron and email handler, every import, and every file the vals read at runtime. Vals that share data or import each other belong in one project.

Make the repository private unless every val in it is public.

Imports and runtime

Easel runs Node.js, so Deno-specific imports and APIs change:

  • Replace npm:, jsr:, and package CDN imports such as https://esm.sh/ with installed packages. Keep the versions that work today.
  • Copy esm.town imports into the repository as local files, crediting the source val.
  • Replace Deno.env.get with process.env, in server code only. Replace other Deno APIs with Node.js or Web APIs, and don't call Deno.serve.
  • Write relative imports with a .js extension, such as ./lib/db.js, even for .ts files. Node.js needs it at runtime.

A Hono app goes in src/index.ts, exported as the default export:

src/index.ts
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => c.text("Hello from Easel"));

export default app;

A val that answers one URL can instead be a file in api/, exporting a function per HTTP method:

api/hello.ts
export function GET(request: Request) {
  const name = new URL(request.url).searchParams.get("name") ?? "world";
  return new Response(`Hello, ${name}!`);
}

Use one approach per project: an api/ folder hides a Hono app's own /api routes.

Files and builds

A function sees only the files bundled with it. Read files with a path built from import.meta.url, such as readFileSync(new URL("../data/cities.json", import.meta.url)), or import JSON directly.

Val Town utilityReplacement
readFileA bundled file read, or a JSON or text import
listFiles, listFilesByPathA list generated at build time
serveFileA file in public/, served by the CDN
readFileTranspiled, fetchTranspiledJavaScriptA Vite build
serveImmutableFile, immutableFileUrlContent-hashed build assets
hotReloadYour framework's dev server
httpEndpoint, emailAddressAn environment variable with the new URL or address

Files in public/ are served before your app, so with Hono, serve the home page from a route instead of public/index.html. Store anything written at runtime in a database or object store; a function's filesystem is temporary.

Frameworks

  • React and Hono: keep the Vite frontend and the Hono routes.
  • Hono JSX: keep server rendering and the JSX settings.
  • Better Auth: keep the routes, secret, and user records, and update trusted origins and callback URLs.
  • Basic HTML: deploy the static files.

Verify

Deploy a preview from the repository:

npx easel-sh link
npx easel-sh deploy

Run link first, so the first deployment is a preview rather than production. Then request each route on the deployment URL with the methods it accepts, and check the project's logs for errors.

Next, move the app's environment variables.