We initially launched Cloudflare Workers with support for JavaScript and languages that compile to WebAssembly, such as Rust, C, and C++. Since then, Cloudflare and the community have improved the usability of Typescript on Workers. But we haven't talked much about the many other popular languages that compile to JavaScript. Today, we’re excited to announce support for Python, Scala, Kotlin, Reason and Dart.
You can build applications on Cloudflare Workers using your favorite language starting today.
Getting Started
Getting started is as simple as installing Wrangler, then running generate for the template for your chosen language: Python, Scala, Kotlin, Dart, or Reason. For Python, this looks like:
wrangler generate my-python-project https://github.com/cloudflare/python-worker-hello-world
Follow the installation instructions in the README inside the generated project directory, then run wrangler publish
. You can see the output of your Worker at your workers.dev subdomain, e.g. https://my-python-project.cody.workers.dev/. You can sign up for a free Workers account if you don't have one yet.
That’s it. It is really easy to write in your favorite languages. But, this wouldn’t be a very compelling blog post if we left it at that. Now, I’ll shift the focus to how we added support for these languages and how you can add support for others.
How it all works under the hood
Language features are important. For instance, it's hard to give up the safety and expressiveness of pattern matching once you've used it. Familiar syntax matters to us as programmers.
You may also have existing code in your preferred language that you'd like to reuse. Just keep in mind that the advantages of running on V8 come with the limitation that if you use libraries that depend on native code or language-specific VM features, they may not translate to JavaScript. WebAssembly may be an option in that case. But for memory-managed languages you're usually better off compiling to JavaScript, at least until the story around garbage collection for WebAssembly stabilizes.
I'll walk through how the Worker language templates are made using a representative example of a dynamically typed language, Python, and a statically typed language, Scala. If you want to follow along, you'll need to have Wrangler installed and configured with your Workers account. If it's your first time using Workers it's a good idea to go through the quickstart.
Dynamically typed languages: Python
You can generate a starter "hello world" Python project for Workers by running
wrangler generate my-python-project https://github.com/cloudflare/python-worker-hello-world
Wrangler will create a my-python-project
directory and helpfully remind you to configure your account_id in the wrangler.toml file inside it. The README.md file in the directory links to instructions on setting up Transcrypt, the Python to JavaScript compiler we're using. If you already have Python 3.7 and virtualenv installed, this just requires running
cd my-python-project
virtualenv env
source env/bin/activate
pip install transcrypt
wrangler publish
The main requirement for compiling to JavaScript on Workers is the ability to produce a single js file that fits in our bundle size limit of 1MB. Transcrypt adds about 70k for its Python runtime in this case, which is well within that limit. But by default running Transcrypt on a Python file will produce multiple JS and source map files in a __target__
directory. Thankfully Wrangler has built in support for webpack. There's a webpack loader for Transcrypt, making it easy to produce a single file. See the webpack.config.js file for the setup.
The point of all this is to run some Python code, so let's take a look at index.py:
def handleRequest(request):
return __new__(Response('Python Worker hello world!', {
'headers' : { 'content-type' : 'text/plain' }
}))
addEventListener('fetch', (lambda event: event.respondWith(handleRequest(event.request))))
In most respects this is very similar to any other Worker hello world, just in Python syntax. Dictionary literals take the place of JavaScript objects, lambda
is used instead of an anonymous arrow function, and so on. If using __new__
to create instances of JavaScript classes seems awkward, the Transcrypt docs discuss an alternative.
Clearly, addEventListener
is not a built-in Python function, it's part of the Workers runtime. Because Python is dynamically typed, you don't have to worry about providing type signatures for JavaScript APIs. The downside is that mistakes will result in failures when your Worker runs, rather than when Transcrypt compiles your code. Transcrypt does have experimental support for some degree of static checking using mypy.
Statically typed languages: Scala
You can generate a starter "hello world" Scala project for Workers by running
wrangler generate my-scala-project https://github.com/cloudflare/scala-worker-hello-world
The Scala to JavaScript compiler we're using is Scala.js. It has a plugin for the Scala build tool, so installing sbt and a JDK is all you'll need.
Running sbt fullOptJS
in the project directory will compile your Scala code to a single index.js file. The build configuration in build.sbt is set up to output to the root of the project, where Wrangler expects to find an index.js file. After that you can run wrangler publish
as normal.
Scala.js uses the Google Closure Compiler to optimize for size when running fullOptJS
. For the hello world, the file size is 14k. A more realistic project involving async fetch weighs in around 100k, still well within Workers limits.
In order to take advantage of static type checking, you're going to need type signatures for the JavaScript APIs you use. There are existing Scala signatures for fetch and service worker related APIs. You can see those being imported in the entry point for the Worker, Main.scala:
import org.scalajs.dom.experimental.serviceworkers.{FetchEvent}
import org.scalajs.dom.experimental.{Request, Response, ResponseInit}
import scala.scalajs.js
The import of scala.scalajs.js allows easy access to Scala equivalents of JavaScript types, such as js.Array
or js.Dictionary
. The remainder of Main looks fairly similar to a Typescript Worker hello world, with syntactic differences such as Unit instead of Void and square brackets instead of angle brackets for type parameters:
object Main {
def main(args: Array[String]): Unit = {
Globals.addEventListener("fetch", (event: FetchEvent) => {
event.respondWith(handleRequest(event.request))
})
}
def handleRequest(request: Request): Response = {
new Response("Scala Worker hello world", ResponseInit(
_headers = js.Dictionary("content-type" -> "text/plain")))
}
}
Request, Response and FetchEvent are defined by the previously mentioned imports. But what's this Globals object? There are some Worker-specific extensions to JavaScript APIs. You can handle these in a statically typed language by either automatically converting existing Typescript type definitions for Workers or by writing type signatures for the features you want to use. Writing the type signatures isn't hard, and it's good to know how to do it, so I included an example in Globals.scala:
import scalajs.js
import js.annotation._
@js.native
@JSGlobalScope
object Globals extends js.Object {
def addEventListener(`type`: String, f: js.Function): Unit = js.native
}
The annotation @js.native
indicates that the implementation is in existing JavaScript code, not in Scala. That's why the body of the addEventListener
definition is just js.native
. In a JavaScript Worker you'd call addEventListener
as a top-level function in global scope. Here, the @JSGlobalScope
annotation indicates that the function signatures we're defining are available in the JavaScript global scope.
You may notice that the type of the function passed to addEventListener
is just js.Function
, rather than specifying the argument and return types. If you want more type safety, this could be done as js.Function1[FetchEvent, Unit]
. If you're trying to work quickly at the expense of safety, you could use def addEventListener(any: Any*): Any
to allow anything.
For more information on defining types for JavaScript interfaces, see the Scala.js docs.
Using Workers KV and async Promises
Let's take a look at a more realistic example using Workers KV and asynchronous calls. The idea for the project is our own HTTP API to store and retrieve text values. For simplicity's sake I'm using the first slash-separated component of the path for the key, and the second for the value. Usage of the finished project will look like PUT /meaning of life/42
or GET /meaning of life/
The first thing I need is to add type signatures for the parts of the KV API that I'm using, in Globals.scala. My KV namespace binding in wrangler.toml is just going to be named KV, resulting in a corresponding global object:
object Globals extends js.Object {
def addEventListener(`type`: String, f: js.Function): Unit = js.native
val KV: KVNamespace = js.native
}
bash$ curl -w "\n" -X PUT 'https://scala-kv-example.cody.workers.dev/meaning of life/42'
bash$ curl -w "\n" -X GET 'https://scala-kv-example.cody.workers.dev/meaning of life/'
42
So what's the definition of the KVNamespace type? It's an interface, so it becomes a Scala trait with a @js.native
annotation. The only methods I need to add right now are the simple versions of KV.get and KV.put that take and return strings. The return values are asynchronous, so they're wrapped in a js.Promise. I'll make that wrapped string a type alias, KVValue, just in case we want to deal with the array or stream return types in the future:
object KVNamespace {
type KVValue = js.Promise[String]
}
@js.native
trait KVNamespace extends js.Object {
import KVNamespace._
def get(key: String): KVValue = js.native
def put(key: String, value: String): js.Promise[Unit] = js.native
}
With type signatures complete, I'll move on to Main.scala and how to handle interaction with JavaScript Promises. It's possible to use js.Promise
directly, but I'd prefer to use Scala semantics for asynchronous Futures. The methods toJSPromise
and toFuture
from js.JSConverters
can be used to convert back and forth:
def get(key: String): Future[Response] = {
Globals.KV.get(key).toFuture.map { (value: String) =>
new Response(value, okInit)
} recover {
case err =>
new Response(s"error getting a value for '$key': $err", errInit)
}
}
The function for putting values makes similar use of toFuture
to convert the return value from KV into a Future. I use map
to transform the value into a Response, and recover
to handle failures. If you prefer async / await syntax instead of using combinators, you can use scala-async.
Finally, the new definition for handleRequest
is a good example of how pattern matching makes code more concise and less error-prone at the same time. We match on exactly the combinations of HTTP method and path components that we want, and default to an informative error for any other case:
def handleRequest(request: Request): Future[Response] = {
(request.method, request.url.split("/")) match {
case (HttpMethod.GET, Array(_, _, _, key)) =>
get(key)
case (HttpMethod.PUT, Array(_, _, _, key, value)) =>
put(key, value)
case _ =>
Future.successful(
new Response("expected GET /key or PUT /key/value", errInit))
}
}
You can get the complete code for this example by running
wrangler generate projectname https://github.com/cloudflare/scala-worker-kv
How to contribute
I'm a fan of programming languages, and will continue to add more Workers templates. You probably know your favorite language better than I do, so pull requests are welcome for a simple hello world or more complex example.
And if you're into programming languages check out the latest language rankings from RedMonk where Python is the first non-Java or JavaScript language ever to place in the top two of these rankings.
Stay tuned for the rest of Serverless Week!