Meet Lightbug, the First Mojo HTTP Framework

files/photo_2024-01-09 20.08.16

Originally published on Hacker Noon

Mojo is a language that combines readability of Python with the speed of C++. It’s useful for different things, from low-level code that’s close to hardware, via the Back-End API design, to the world of Front-End and the Web — Mojo is powerful enough to scale across the entire modern stack. It’s also been designed with AI and Machine Learning in mind, so it will be particularly useful for AI developers and Data Scientists.

The language is still young, with the ecosystem missing the tools for everyday software development tasks like networking or basic HTTP operations. This is where Lightbug 🔥🐝 flies in.

Lightbug is a simple HTTP framework written in pure Mojo, with no external dependencies by default. It’s meant to serve as a foundation for more complex projects, and allows you to develop Web services like APIs, set up basic routing, or even serve HTML pages with Mojo, while taking advantage of the features of this language, like static typing and great performance.

To get started, simply install Mojo and Git, then clone the Lightbug Github repo:

git clone https://github.com/saviorand/lightbug_http.git

Once it’s cloned, switch to the directory in your command line:

cd lightbug_http

Then run the server (yes, that’s the Mojo file extension! 🔥):

mojo lightbug.🔥

You should see the following line printed to the console:

🔥🐝 Lightbug is listening on 0.0.0.0:8080
Ready to accept connections...

Now you can start making requests to your server or try opening localhost:8080 or 0.0.0.0:8080 in the browser — you should see a welcome screen.

Now, let’s get to some real coding!

While Lightbug is still young, the core functionality people expect to be able to develop for the modern Web is already there. Note that since there’s no package manager yet, you should simply include lightbug_http as a subfolder inside your own project. This will work as a Mojo package, and will allow you to import tools like web primitives, server, and more from Lightbug.

For example, add this to the top of your file to import the server:

from lightbug_http.sys.server import SysServer

This is an implementation in pure Mojo. If you want to use the Python implementation import the PythonServer instead. It will work in the same manner.

To make an HTTP service, simply make a struct that satisfies the HTTPService trait, meaning it has a func method with the following signature:

trait HTTPService:
 fn func(self, req: HTTPRequest) raises -> HTTPResponse:
     ...

This uses built-in primitives to take in an HTTPRequest, execute any custom logic you write in Mojo or Python, and return an HTTPResponse object with some data back to the API consumer.

Let’s make a service that prints all requests sent to 0.0.0.0:8080 to the console.
To do this, create a file called my_awesome_service.🔥 and paste the following:

from lightbug_http import *

@value
struct Printer(HTTPService):
   fn func(self, req: HTTPRequest) raises -> HTTPResponse:
      let body = req.body_raw
      print(String(body))
      return OK(body)

fn main() raises:
    var server = SysServer()
    let handler = Printer()
    server.listen_and_serve("0.0.0.0:8080", handler)

Run mojo my_awesome_service.🔥 and send the request to 0.0.0.0:8080 from your favorite API client, like Insomnia or Bruno. You should see some details about the request printed to the console.

Congrats! You’re officially a Mojo web developer now 🔥.

In the example, we initialize a variable called handler with let (meaning it cannot be re-assigned) and pass it to listen_and_serve as a second parameter for clarity.

Adding a @value decorator is optional: if you’re an advanced Mojician, you can add an __init__ constructor method instead. It will work the same, @value just generates this and other useful methods automatically.

struct Printer(HTTPService):
	fn __init__(inout self):
		print("Printer initialized!")

	fn func(self, req: HTTPRequest) raises -> HTTPResponse:
		let body = req.body_raw	
		print(String(body))
		return OK(body)

You might say, but that’s just one route! Modern APIs require much more than that. Lightbug can also do some basic routing as well:

@value
struct ExampleRouter(HTTPService):
   fn func(self, req: HTTPRequest) raises -> HTTPResponse:
      let body = req.body_raw
      let uri = req.uri()

      if uri.path() == "/":
            print("I'm on the index path!")
      if uri.path() == "/first":
            print("I'm on /first!")
      elif uri.path() == "/second":
            print("I'm on /second!")

      return OK(body)

Add this to your my_awesome_service.🔥 and pass it as a handler to the server:

fn main() raises:
    var server = SysServer()
	let handler = ExampleRouter()
	server.listen_and_serve("0.0.0.0:8080", handler)

You can now open your browser and go to localhost:8080/first, localhost:8080/second to see the changes.

We plan on making routing, as well as other tasks like authoring and generating APIs from an OpenAPI specification, designing your data model, and building web applications even more enjoyable in the future by building lightbug_api and lightbug_web packages. Check out our Roadmap for details.

That’s it for an introduction to Lightbug 🔥🐝! Hope it was useful.

Lightbug is an open-source community project. Please star ⭐ our Github repo , join the Discord, and check out how to contribute with your code, so we can make it even better for the Mojo community. Until next time!