JavaScript at the Edge
Write, run, and test Edge Applications with the open source Fly runtime. When you're ready to ship, deploy to our global hosting infrastructure with a single command.
Source codeHosting> Build an app $ npm install -g @fly/fly $ fly new fly-example -t getting-startedfly.http.respondWith(()=> new Response("Hello World"))> Run it and view http://localhost:3000 in your browser $ fly server fly-example > Deploy it $ fly --app [app_name] deploy
Quickstart Applications
import { Image } from '@fly/image' async function watermarkPicture() { const [picture, logo] = await Promise.all([ loadImage(pictureURL), loadImage(logoURL) ]) const meta = picture.metadata() const padPct = 0.1 const padding = { top: parseInt(meta.height * padPct), bottom: parseInt(meta.height * padPct), left: parseInt(meta.width * padPct), right: parseInt(meta.width * padPct) } logo.extend(padding).background({ r: 0, g: 0, b: 0, alpha: 0 }) picture.overlayWith(logo, { gravity: Image.gravity.southeast }) const body = await picture.toBuffer() return new Response(body.data, { headers: { "Content-Type": "image/jpg" } }) } async function loadImage(url) { const resp = await fetch(url) if (resp.status != 200) { throw new Error("Couldn't load image: " + url) } const body = await resp.arrayBuffer() return new Image(body) }
import proxy from '@fly/proxy' // the Glitch App URL const glitchHost = "fly-example.glitch.me" /* Deployment instructions */ // fly apps create <app-name> // fly deploy // fly hostnames add <your-custom-hostname.com> // create a proxy fetch function to Glitch const glitch = proxy(`https://${glitchHost}`, { headers: { 'host': glitchHost, // set HOST header to glitchHost 'x-forwarded-host': false // Don't send x-forwarded-host header } }) fly.http.respondWith(function (req) { // redirect to https let resp = requireSSL(req) if (resp) return resp // proxy to glitch return glitch(req) }) function requireSSL(req) { const url = new URL(req.url) if (url.protocol != "https:") { url.protocol = "https" url.port = '443' if (app.env === "development") { console.log("Skipping SSL redirect in dev mode:", url.toString()) } else { return new Response("Redirecting", { status: 301, headers: { "Location": url.toString() } }) } } }
export default function balancer(backends: FetchFn[]) { const tracked = backends.map((h) => { if (typeof h !== "function") { throw Error("Backend must be a fetch like function") } return <Backend>{ proxy: h, requestCount: 0, scoredRequestCount: 0, statuses: Array<number>(10), lastError: 0, healthScore: 1, errorCount: 0 } }) const fn = async function fetchBalancer(req: RequestInfo, init?: RequestInit | undefined): Promise<Response> { if (typeof req === "string") { req = new Request(req) } const attempted = new Set<Backend>() while (attempted.size < tracked.length) { let backend: Backend | null = null const [backendA, backendB] = chooseBackends(tracked, attempted) if (!backendA) { return new Response("No backend available", { status: 502 }) } if (!backendB) { backend = backendA } else { // randomize between 2 good candidates backend = (Math.floor(Math.random() * 2) == 0) ? backendA : backendB } const promise = backend.proxy(req, init) if (backend.scoredRequestCount != backend.requestCount) { // fixup score // this should be relatively concurrent with the fetch promise score(backend) } backend.requestCount += 1 attempted.add(backend) let resp: Response try { resp = await promise } catch (e) { resp = proxyError } if (backend.statuses.length < 10) { backend.statuses.push(resp.status) } else { backend.statuses[(backend.requestCount - 1) % backend.statuses.length] = resp.status } if (resp.status >= 500 && resp.status < 600) { backend.lastError = Date.now() // always recompute score on errors score(backend) // clear out response to trigger retry if (canRetry(req, resp)) { continue } } return resp } return proxyError } return Object.assign(fn, { backends: tracked }) } const proxyError = new Response("couldn't connect to origin", { status: 502 }) export interface FetchFn { (req: RequestInfo, init?: RequestInit | undefined): Promise<Response> } /** * Represents a backend with health and statistics. */ export interface Backend { proxy: (req: RequestInfo, init?: RequestInit | undefined) => Promise<Response>, requestCount: 0, scoredRequestCount: 0, statuses: number[], lastError: number, healthScore: number, errorCount: 0 } // compute a backend health score with time + status codes function score(backend: Backend, errorBasis?: number) { if (typeof errorBasis !== "number" && !errorBasis) errorBasis = Date.now() const timeSinceError = (errorBasis - backend.lastError) const statuses = backend.statuses const timeWeight = (backend.lastError === 0 && 0) || ((timeSinceError < 1000) && 1) || ((timeSinceError < 3000) && 0.8) || ((timeSinceError < 5000) && 0.3) || ((timeSinceError < 10000) && 0.1) || 0; if (statuses.length == 0) return 0 let requests = 0 let errors = 0 for (let i = 0; i < statuses.length; i++) { const status = statuses[i] if (status && !isNaN(status)) { requests += 1 if (status >= 500 && status < 600) { errors += 1 } } } const score = (1 - (timeWeight * (errors / requests))) backend.healthScore = score backend.scoredRequestCount = backend.requestCount return score } function canRetry(req: Request, resp: Response) { if (resp && resp.status < 500) return false // don't retry normal boring errors or success if (req.method == "GET" || req.method == "HEAD") return true return false } function chooseBackends(backends: Backend[], attempted?: Set<Backend>) { let b1: Backend | undefined let b2: Backend | undefined for (let i = 0; i < backends.length; i++) { const b = backends[i] if (attempted && attempted.has(b)) continue; if (!b1) { b1 = b continue } if (!b2) { b2 = b continue } const old1 = b1 b1 = bestBackend(b, b1) if (old1 != b1) { // b1 got replaced, make sure it's not better b2 = bestBackend(old1, b2) } else { b2 = bestBackend(b, b2) } } return [b1, b2] } function bestBackend(b1: Backend, b2: Backend) { if ( b1.healthScore > b2.healthScore || (b1.healthScore == b2.healthScore && b1.requestCount < b2.requestCount) ) { return b1 } return b2 } export const _internal = { chooseBackends, score }
Edge Application Hosting
We run datacenters all over the world. Deploy your app to our infrastructure and we'll route your users to the nearest datacenter. Our application servers connect them to your JavaScript environment in under 1ms. Your code does the rest.
Deploy to production
Feel free to read about our pricing, and when you're comfy give us your email address and we'll get you started.
Features
Our open source Edge Application runtime works like most modern application frameworks. We have tools for development + test, and powerful APIs for solving problems at the Edge.
Modern JavaScript

- Write modern JavaScript with all the async/await goodness available in ES2017
- Target the same APIs you use in the browser: fetch, Response, Request, Streams, and more
- Import other peoples' modules for faster development
- Deliver http requests fast using
@fly/cache - Resize, crop, and edit images with the
@fly/imagelibrary - Modify content on every request: rewrite html, compress assets, and more.
- Develop, run, and test locally using fly deploy/test
- Staging and production environments
- Build an integrated continuous integration pipeline
Lightning fast Edge Libraries

Full development environment
