惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
AI
AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
Vercel News
Vercel News
Stack Overflow Blog
Stack Overflow Blog
P
Proofpoint News Feed
Microsoft Security Blog
Microsoft Security Blog
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
Martin Fowler
Martin Fowler
博客园 - 【当耐特】
B
Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
F
Full Disclosure
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Help Net Security
Recorded Future
Recorded Future
N
News and Events Feed by Topic
雷峰网
雷峰网
V
Vulnerabilities – Threatpost
Schneier on Security
Schneier on Security
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
O
OpenAI News
Project Zero
Project Zero
罗磊的独立博客
G
GRAHAM CLULEY
腾讯CDC
P
Privacy International News Feed
V
V2EX
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
H
Heimdal Security Blog
L
LINUX DO - 热门话题
Forbes - Security
Forbes - Security
美团技术团队
MongoDB | Blog
MongoDB | Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence
T
Tor Project blog
Cisco Talos Blog
Cisco Talos Blog
宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog

Flavio Copes

Workers Cache: a cache in front of your Cloudflare Worker Cloudflare Drop: drag a folder, get a live site Temporary Cloudflare accounts: agents can now deploy without signing up Moondream 3.1 on Workers AI: fast vision at the edge inferencecost.dev: what will AI inference cost you at 10k users? Sitebase: all the features your website needs, in one place StackPlan: figure out where to deploy your app, and what it How the Cloudflare Pages build cache works The Summer of Code How I generate an Open Graph image for every post New: 90 free tools for developers How I added search to my static site with Pagefind How to rebuild a Cloudflare site on a schedule Cloudflare Email Workers: run code when an email arrives Cloudflare Turnstile: stop bots without annoying CAPTCHAs Cloudflare Workers: secrets and environments Cloudflare Workers observability: logs and traces Cloudflare Analytics Engine: store and query metrics The AI Workshop (July 2026 cohort) Cloudflare Cron Triggers: run a Worker on a schedule Cloudflare Durable Objects: state that lives in one place Cloudflare Queues: run work in the background Cloudflare R2: object storage without egress fees Cloudflare KV: a key-value store for your Workers Cloudflare D1: a SQL database for your Workers Serving a website with Cloudflare Workers static assets Wrangler: the Cloudflare Workers command line tool Executor: one gateway to connect your AI agent to every tool Cloudflare Workers: your first serverless function Vercel eve: an open framework for building AI agents Flue: the open framework for building AI agents A hands-on guide to The Agency, a collection of AI agents The AI Workshop (June 2026 cohort) The AI Workshop (May 2026 cohort) The AI Workshop (Apr 2026 cohort)
Val Town: write and deploy code in seconds
Flavio Copes · 2026-06-12 · via Flavio Copes

By Flavio Copes

A hands-on tutorial for Val Town, where you write JavaScript and deploy it to a live URL instantly, with HTTP, cron, and email triggers plus SQLite storage.

~~~

You write a bit of code, hit save, and a second later it’s live on a URL. No server to set up, no deploy step, no config files.

That’s Val Town.

I use it for small APIs, webhooks, and scheduled scripts. It’s the fastest way I know to go from an idea to code running on the web.

In this post we’ll build a few things with it, from your first endpoint to storing data and sending email.

What is Val Town?

Val Town is a place to write JavaScript and TypeScript in the browser. When you save, your code is live on a URL.

The unit of code is called a val. Think of a val as a small folder of code that runs in the cloud. Like a GitHub repo, except it actually runs.

A file inside a val can have a trigger. The trigger is what makes your code run. There are three:

  • HTTP — the code runs when someone visits a URL. This is how you build APIs and websites.
  • Cron — the code runs on a schedule.
  • Email — the code runs when an email arrives at a special address.

Val Town also gives you the things you usually need, ready to go: a database (SQLite), file storage, and a way to send email. Nothing to set up.

It runs on Deno, so you write modern JavaScript and TypeScript with web-standard APIs.

Why I like it

It’s fast. You save, it’s live. The loop is seconds, not minutes.

Every save is a new version, so you can roll back the moment you break something.

There’s no infrastructure to manage. The database, file storage, email, cron, HTTPS, the URL, it’s all there already.

I reach for Val Town for APIs, webhooks, glue code between services, scheduled jobs, and quick demos. I wouldn’t use it for heavy or long-running work. It’s made for small, fast things.

Getting started

Go to val.town and sign up. It’s free, no credit card.

You’ll land in the editor. That’s where you write and run vals, right in the browser.

Your first val

Create a new val and give it an HTTP trigger, by clicking the + button at the top right of the editor and picking HTTP.

An HTTP val is a function. It gets a Request and returns a Response:

export default async function (req: Request): Promise<Response> {
  return new Response('Hello from Val Town!')
}

Save it. Val Town hands you a URL, and opening it shows your message. That’s a live web server in about 100ms.

If you’ve used the Fetch API or Cloudflare Workers, this is the same Request and Response you already know.

Let’s return JSON instead:

export default async function (req: Request): Promise<Response> {
  return Response.json({ ok: true })
}

Response.json() sets the content type and turns the object into JSON for you.

You can also read from the request. Here we grab a query parameter:

export default async function (req: Request): Promise<Response> {
  const url = new URL(req.url)
  const name = url.searchParams.get('name') || 'stranger'
  return new Response(`Hello, ${name}!`)
}

Add ?name=flavio to your URL and it greets you by name. It’s all standard web APIs, nothing Val Town specific.

Building an API

One route in a plain function is fine. For multiple routes, a framework helps.

Val Town works well with Hono, a tiny web framework. You don’t install it, you import it straight from npm:

import { Hono } from 'npm:hono'

const app = new Hono()

app.get('/', (c) => c.text('Home'))
app.get('/about', (c) => c.text('About'))

export default app.fetch

Look at the last line. app.fetch is a function that takes a Request and returns a Response, which is exactly what an HTTP val expects. So we hand it over as the default export.

You can read route parameters too:

app.get('/users/:name', (c) => {
  const name = c.req.param('name')
  return c.json({ user: name })
})

Visit /users/flavio and you get { "user": "flavio" }.

You can return HTML by setting the content type and returning a string:

export default async function (): Promise<Response> {
  return new Response('<h1>My page</h1>', {
    headers: { 'Content-Type': 'text/html' },
  })
}

Val Town supports JSX too, if you want to build HTML with components. A plain string is enough to start.

Storing data with SQLite

Most apps need to store something. Every val comes with its own private SQLite database. Each val gets its own, so two vals don’t share data unless you want them to.

Import it from the standard library:

import { sqlite } from 'https://esm.town/v/std/sqlite/main.ts'

Let’s build a tiny guestbook. First, the table:

await sqlite.execute(`create table if not exists messages (
  id integer primary key autoincrement,
  text text,
  created_at text default current_timestamp
)`)

create table if not exists means it’s safe to run every time. It creates the table once and skips it after that.

To add a row, pass your SQL with arguments. The ? is a placeholder for a value:

await sqlite.execute({
  sql: 'insert into messages (text) values (?)',
  args: ['Hello world!'],
})

Always use placeholders for values. They keep user input from messing with your query, which is how SQL injection happens.

To read the data back:

const result = await sqlite.execute('select text from messages order by id desc')
console.log(result.rows)

The rows are in result.rows. Each row is an object with your column names as keys.

Now let’s put it in one HTTP val that lists messages and lets you add them:

import { sqlite } from 'https://esm.town/v/std/sqlite/main.ts'

await sqlite.execute(`create table if not exists messages (
  id integer primary key autoincrement,
  text text
)`)

export default async function (req: Request): Promise<Response> {
  if (req.method === 'POST') {
    const { text } = await req.json()
    await sqlite.execute({
      sql: 'insert into messages (text) values (?)',
      args: [text],
    })
  }

  const result = await sqlite.execute('select text from messages order by id desc')
  return Response.json(result.rows)
}

That’s a full API with persistent storage in about 15 lines. POST a text field to add a message, GET to list them.

Storing files with blob storage

SQLite is for structured data. When you want to stash a chunk of something, like JSON, an image, or a text file, use blob storage.

Import it the same way:

import { blob } from 'https://esm.town/v/std/blob/main.ts'

The simplest use is reading and writing JSON:

await blob.setJSON('settings', { theme: 'dark' })
const settings = await blob.getJSON('settings')

getJSON returns undefined if the key isn’t there, so you can check for that.

You can list keys, optionally by prefix:

const keys = await blob.list('app_')

And delete one:

await blob.delete('settings')

I use blob storage a lot for small bits of state, like the last time a cron job ran.

Sending email

Val Town can send email in one call. This is one of my favorite features.

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

await email({
  subject: 'Hello from Val Town',
  text: 'This is a test email sent from a val.',
})

By default the email goes to you, the account owner. So sending yourself a notification is as easy as a console.log.

Note: on the free plan you can only email yourself. On Pro you can email anyone, with the to, cc, and bcc fields.

Send HTML instead of text if you want:

await email({
  subject: 'Your daily report',
  html: '<h1>Today</h1><p>All systems normal.</p>',
})

Running code on a schedule

A cron val runs on a timer. Good for reminders, polling a feed, or syncing data.

Give a file a CRON trigger, then write the function that runs:

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

export default async function () {
  await email({
    subject: 'Good morning!',
    text: 'Time to start the day.',
  })
}

When you add the trigger, you pick the schedule. Either a simple interval like “once an hour”, or a cron expression if you need more control.

This expression runs every two hours, on the first day of the month:

0 */2 1 * *

Be careful with time zones. Cron in Val Town runs in UTC, so adjust if you mean a specific hour in your local time.

The function also gets an interval argument with the time it last ran. That’s handy for “what’s changed since last time” jobs:

export default async function (interval: Interval) {
  console.log('Last run was at', interval.lastRunAt)
}

Receiving email

This is sending in reverse. An email val gets its own address, and your code runs whenever a message lands there.

Give a file an EMAIL trigger. The function gets the email as its argument:

export default async function (message: Email) {
  console.log('From:', message.from)
  console.log('Subject:', message.subject)
  console.log('Body:', message.text)
}

Val Town gives the val an address like [email protected]. Send a message there and the function runs.

The message object has what you’d expect: from, to, subject, text, html, and attachments.

Attachments are File objects, so you can read them:

export default async function (message: Email) {
  for (const file of message.attachments) {
    console.log(file.name, file.type)
    const content = await file.text()
    console.log(content)
  }
}

Put sending and receiving together and you can build email automations. Forward a message, post it to Slack, hand it to an AI. A few lines each.

Importing npm packages

You saw this with Hono. Val Town supports thousands of npm packages, with no install step. You just import them:

import { Hono } from 'npm:hono'
import dayjs from 'npm:dayjs'

The npm: prefix tells Deno to fetch the package. No package.json, no node_modules.

You can also import from Deno’s registry, and from other people’s vals using https://esm.town/ URLs, like we did for the standard library.

Keeping secrets out of your code

You’ll often need a secret, like an API key. Don’t hardcode it in your val, because vals can be public.

Set it in your environment variables (there’s a section for this in the Val Town interface), then read it in code:

const apiKey = Deno.env.get('OPENAI_API_KEY')

Working from your own editor

The browser editor is great, but sometimes you want your own. Val Town has a CLI called vt for that.

It runs on Deno, so install Deno first, then the CLI:

deno install -grAf jsr:@valtown/vt

Run vt once to log in.

Now create a val as a local folder:

vt create my-api
cd my-api

Edit the files in your editor, then push to go live:

vt push

The best part is vt watch. It syncs your changes every time you save:

vt watch

So you keep the save-and-it’s-live feeling, but in VS Code, Cursor, or wherever you work.

vt feels like git. You can clone a val, make branches, switch between them, and stream logs from your terminal:

vt clone username/valName
vt branch
vt tail

Letting AI write your vals

Val Town has a built-in agent called Townie that writes and edits vals for you, in the browser.

If you’d rather use your own tools, there’s a plugin that connects Val Town to Claude Code, Codex, and Cursor:

npx plugins add val-town/plugins

Now your AI assistant can create vals, edit them, read your SQLite data, and check logs, from your editor.

As always, read the code your AI writes, especially once it can touch your data.

Pricing

The free plan is genuinely useful. You get HTTP, cron, email (to yourself), SQLite, and blob storage.

Pro adds what you need as you grow: emailing anyone, private vals, more storage, longer runs, and crons as often as every minute.

For learning and side projects, free is plenty.

Try it

The thing that keeps me coming back is the speed. There’s barely a gap between an idea and having it live.

So open the editor and build something small. A webhook, a reminder, a tiny API. You’ll have it running before you finish your coffee.

Start at val.town, it’s free to try.

~~~

Related posts about services: