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-started
fly.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)
}
Watermark ImagesWatermark your company's photosView app on Github
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() }
      })
    }
  }
}
Glitch Witch avatarPut your Glitch project on a custom domainView app on Github
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
}
Load balancer avatarBuild a global application load balancerView app on Github

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.

Our customers

Trusted by your favorite companies to serve thousands of hostnames and millions of requests everyday.

We wanted to do things you just can't do with a normal CDN. Then we found Fly.io. With their programmable CDN, our Font Awesome assets are faster and easier to control. We love it.

DaveDave Gandy

Our community partners

We support our community by sponsoring the open source projects that keep us going.

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

    Modern JavaScript spot graphic
    • 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
  • Lightning fast Edge Libraries

    Edge API spot graphic
    • Deliver http requests fast using @fly/cache
    • Resize, crop, and edit images with the @fly/image library
    • Modify content on every request: rewrite html, compress assets, and more.

    Full development environment

    Development environment spot graphic
    • Develop, run, and test locally using fly deploy/test
    • Staging and production environments
    • Build an integrated continuous integration pipeline