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(),
});
}
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}`);
}
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 });
}
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)}...`,
});
}
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;
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)