DEV Community

Alex Spinov
Alex Spinov

Posted on

Val Town Has a Free API: Deploy Serverless TypeScript Functions in Seconds

Val Town is a social platform for writing and deploying serverless TypeScript functions — each function gets an instant HTTP endpoint, cron schedule, or email handler with zero configuration.

Why Val Town Matters

Deploying a simple API endpoint shouldn't require Docker, Kubernetes, CI/CD, or even a GitHub repo. Val Town gives every function its own URL the moment you save it.

What you get for free:

  • Instant HTTP endpoints for any TypeScript function
  • Built-in cron scheduling (run functions on a schedule)
  • Email handling (receive and send emails from functions)
  • SQLite database (persistent storage per account)
  • Import any npm package — no package.json needed
  • Version history and forking (like GitHub for functions)
  • Free tier: 5,000 runs/day

Quick Start: HTTP Endpoint

// This gets an instant URL: https://username-functionname.web.val.run
export default async function(req: Request): Promise<Response> {
  const url = new URL(req.url);
  const name = url.searchParams.get("name") || "World";

  return Response.json({
    message: `Hello, ${name}!`,
    timestamp: new Date().toISOString(),
  });
}
Enter fullscreen mode Exit fullscreen mode

Save it → instant live URL. No deploy step.

Cron Jobs

// Runs every hour automatically
export default async function() {
  const response = await fetch("https://api.github.com/repos/denoland/deno/releases/latest");
  const release = await response.json();

  // Store in Val Town SQLite
  const { sqlite } = await import("https://esm.town/v/std/sqlite");
  await sqlite.execute({
    sql: "INSERT INTO releases (name, published_at) VALUES (?, ?)",
    args: [release.name, release.published_at],
  });

  console.log(`Tracked: ${release.name}`);
}
Enter fullscreen mode Exit fullscreen mode

Built-in SQLite Database

import { sqlite } from "https://esm.town/v/std/sqlite";

// Create table
await sqlite.execute(`
  CREATE TABLE IF NOT EXISTS bookmarks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    url TEXT NOT NULL,
    title TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

// CRUD API
export default async function(req: Request): Promise<Response> {
  const url = new URL(req.url);

  if (req.method === "GET") {
    const rows = await sqlite.execute("SELECT * FROM bookmarks ORDER BY created_at DESC");
    return Response.json(rows);
  }

  if (req.method === "POST") {
    const body = await req.json();
    await sqlite.execute({
      sql: "INSERT INTO bookmarks (url, title) VALUES (?, ?)",
      args: [body.url, body.title],
    });
    return Response.json({ success: true });
  }

  return new Response("Method not allowed", { status: 405 });
}
Enter fullscreen mode Exit fullscreen mode

Email Handler

import { email } from "https://esm.town/v/std/email";

// Receive emails at username.functionname@valtown.email
export async function emailHandler(message: {
  from: string;
  subject: string;
  text: string;
}) {
  console.log(`Email from ${message.from}: ${message.subject}`);

  // Process and reply
  await email({
    to: message.from,
    subject: `Re: ${message.subject}`,
    text: `Thanks for your message! We received: ${message.text.slice(0, 100)}...`,
  });
}
Enter fullscreen mode Exit fullscreen mode

Import Any npm Package

import { Hono } from "npm:hono";
import { z } from "npm:zod";

const app = new Hono();

const UserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
});

app.post("/users", async (c) => {
  const body = await c.req.json();
  const user = UserSchema.parse(body);
  return c.json({ created: user });
});

app.get("/health", (c) => c.json({ status: "ok" }));

export default app.fetch;
Enter fullscreen mode Exit fullscreen mode

Useful Links


Building automated data workflows? Check out my developer tools on Apify for ready-made web scrapers, or email spinov001@gmail.com for custom solutions.

Top comments (0)