Creating an API with Handy-Httpd

Written on , by Andrew Lalis.

Introduction

When I started programming in the D language, I was coming fresh from the world of Java and Spring, where setting up a new web API for a project was pretty much as simple as creating a new method annotated with @GetMapping. In the D world, the closest thing to an all-encompassing web framework is Vibe.d, which is quite convoluted (from a beginner's perspective), and is heavily focused on its own ecosystem and async implementations. That gives it a sort of "walled-garden" feel. There were other, smaller HTTP web server libraries, but none were well documented. So in the end, I set out to build my own HTTP server, which then evolved into more of an HTTP web framework. That's Handy-Httpd.

Handy-Httpd was built on the concept of an HttpRequestHandler, a single unit that takes an HTTP request and does stuff with it.


				class ExampleHandler : HttpRequestHandler {
					void handle(ref HttpRequestContext ctx) {
						ctx.response.writeBodyString("Hello world!");
					}
				}
			
Here's an example of an HttpRequestHandler that just responds with "Hello world!" to any request.

With this request handler, and the principle of composition, we can build up a complete web framework from a few well-purposed handlers.

Setting Up

As with any web server, there's some startup and configuration that needs to be done to get everything working, so we'll get that out of the way first.

We need to create a new D project, and add handy-httpd as a dependency. Do so with the dub CLI tool, or however you prefer to create D projects. Then we'll set up the basic HTTP server in our project's main file.


				import handy_httpd;
				import handy_httpd.handlers.path_delegating_handler;

				void main() {
					ServerConfig config = ServerConfig.defaultValues();
					config.workerPoolSize = 3;
					PathDelegatingHandler pathHandler = new PathDelegatingHandler();
					// TODO: Add mappings to pathHandler
					HttpServer server = new HttpServer(pathHandler, config);
					server.start();
				}
			

The typical boilerplate consists of three main things:

  1. Configuration
  2. Building our request handler
  3. Starting the server

For illustrations' sake, I've configured this server to use 3 workers in its pool for handling requests. You might need more depending on your traffic. I've also created a new PathDelegatingHandler which will serve as the basis for the API's set of endpoints. Check out the documentation on this handler for a detailed explanation of what it can do; in short, we can register new API endpoints to it.

Adding an Endpoint

Now that our server is set up, all we need to do is define some endpoints for users to interact with. This is as simple as creating an HttpRequestHandler and registering it with our pathHandler that we defined on line 7.

To keep things simple to start, we'll add a status endpoint that just returns the string "online". For something this basic, there's no need to create a whole new class; instead, we'll just define a function.


				void handleStatus(ref HttpRequestContext ctx) {
					ctx.response.writeBodyString("online");
				}
			

And then, we'll register it with our path handler so that GET requests to /status will be directed to the handleStatus function.


				pathHandler.addMapping(Method.GET, "/status", &handleStatus);
			

Done! We can now run our project and navigate to localhost:8080/status, and we should see the text "online". It's that simple.

Posting Data to Our API

A GET endpoint is easy enough, but making an endpoint that accepts the user's data isn't too hard either.


				void receivePost(ref HttpRequestContext ctx) {
					JSONValue content = ctx.request.readBodyAsJson();
					// Do stuff with the content.
				}
			
Back to Articles