Building a Dart server from scratch

Suragch
Suragch
Dec 5, 2020 · 8 min read

Part 1 in a series about Server Side Dart without a framework

I really like being able to use the same language to write server code as when I write Flutter apps. Two of the main frameworks to do that have been Aqueduct and Angel. Unfortunately, Angel is being deprecated and Aqueduct hasn’t published a stable version for some time (as of December 2020).

Since the A’s are out, it’s time for plan B.

While frameworks are nice, there’s always a bit of magic about them. The dart:io library, which is one of the core Dart libraries, already includes the low-level classes and functions needed to make an HTTP server. So this article will teach you how to create such a server yourself.

If successful, this may turn into a series of articles. Or if Aqueduct or another Dart framework comes back online, then I’ll probably point you in that direction.

Let’s get started. The full code is at the end of the article if you get lost along the way.

Setup

flutter channel beta

Or with a beta version of the Dart SDK.

Now create a new Dart project called my_server (or whatever you like) on the command line:

dart create my_server

Open that folder with your preferred IDE. VS Code and IntelliJ both have a Dart plugin. Install it if you haven’t already.

Creating a server

import 'dart:io';Future<void> main() async {
final server = await createServer();
print('Server started: ${server.address} port ${server.port}');
}
Future<HttpServer> createServer() async {
final address = InternetAddress.loopbackIPv4;
const port = 4040;
return await HttpServer.bind(address, port);
}

This creates a server that listens to the localhost IP address (127.0.0.1) on port 4040.

You can start your server from the terminal like this:

dart run bin/my_server.dart

You should see the following printout:

Server started: InternetAddress('127.0.0.1', IPv4) port 4040

Congratulations! You’ve already made a Dart server. That was pretty easy, wasn’t it?

You’ve started the server, but it doesn’t really do anything yet. Force close your program by pressing Control+C.

Handling HTTP requests

Replace the main function with the following code:

Future<void> main() async {
final server = await createServer();
print('Server started: ${server.address} port ${server.port}');
await handleRequests(server);
}

And add handleRequests as a top-level function in my_server.dart:

Future<void> handleRequests(HttpServer server) async {
await for (HttpRequest request in server) {
request.response.write('Hello from a Dart server');
await request.response.close();
}
}

Notes:

  • The HttpServer class implements Stream<HttpRequest>. That means you can treat it as a stream to handle the requests one by one as they come in.
  • Your handleRequests method ignores the type of request. It just gives the same response to everything: first by writing a string (that will be returned in the response body) and then by closing the response, which sends it back to the requester.

Run your program again, and then open the following address in your browser:

You should see the following result:

Your browser made a GET request to do that. You’ll see how to route different kinds of requests next.

Routing different kinds of requests

Future<void> handleRequests(HttpServer server) async {
await for (HttpRequest request in server) {
switch (request.method) {
case 'GET':
handleGet(request);
break;
case 'POST':
handlePost(request);
break;
default:
handleDefault(request);
}
}
}

Now for each request that comes in, you route it to a different method. The next few sections will look at handling GET, POST, and anything else.

We’ll implement handleGet first, so temporarily comment out handlePost and handleDefault:

      case 'POST':
// handlePost(request);
break;
default:
// handleDefault(request);

Handling GET requests

var myStringStorage = 'Hello from a Dart server';void handleGet(HttpRequest request) {
request.response
..write(myStringStorage)
..close();
}

Notes:

  • The myStringStorage global variable is here to represent a database. We’re reading from that variable here and will be writing to it in the next section.
  • A GET request shouldn’t change the server state, so we just pass back the value of myStringStorage in our response.

Save your work and restart the server. Then open the following address again in your browser:

You should see the same result as before:

Handling POST requests

Uncomment the handlePost line in handleRequests and then add the following top-level function to my_server.dart:

Future<void> handlePost(HttpRequest request) async {
myStringStorage = await utf8.decoder.bind(request).join();
request.response
..write('Got it. Thanks.')
..close();
}

You’ll also need to add the following import:

import 'dart:convert';

Notes:

  • The first line in the body of handlePost takes the incoming chunks of data from the request, converts them to UTF-8 formatted strings and joins them into a single string.
  • Once you have that string, this method uses it to update the value of the global variable myStringStorage. This is symbolic of writing to a database.
  • Since we’re actually just updating an existing value rather than creating a new one, it might make more sense to define the REST API to use PUT rather than POST. But POST is okay for our example.
  • There isn’t any security at all yet. If you put this server on the web, anyone in the world can update myStringStorage. In a future article I’d like to talk about authentication and authorization. For now you can read Authentication with server side Dart.

Save your work and restart the server.

You can’t make POST requests with your browser, so you need another tool like curl or Postman. You can learn about these and other ways to make POST requests by reading this article.

I’ll use Postman to make the POST request. Open Postman and perform the following steps:

  1. Select POST as the type of request.
  2. Write http://localhost:4040 in the address bar.
  3. Choose the Body tab.
  4. Write any string, for example Hello.
  5. Click the Send button.

You should receive a 200 OK response from the server with Got it. Thanks. in the body of the response.

Response from the server as seen in Postman

If you run another GET request (either in your browser or with Postman), you should see the updated value of myStringStorage:

Nice! You successfully updated the server from a client.

Handling other requests

Uncomment the handleDefault line and then add the following top-level method:

void handleDefault(HttpRequest request) {
request.response
..statusCode = HttpStatus.methodNotAllowed
..write('Unsupported request: ${request.method}.')
..close();
}

Notes:

  • This time you’re setting the status code to methodNotAllowed. That translates to a code of 405.
  • You write an error in the response and then send it back to the client.

Save your work and restart the server.

Test it out in Postman by sending a PUT request.

You’ll get the following response:

Error response from server as seen in Postman

Good work. You’ve made a solid start to building your own server. You’ll find the full code below. First, though, look at some steps to take next.

Going on

Unless you’re only supporting simple GET requests for a small amount of public data, there are a few major missing pieces that need to be solved before you can use Dart as a backend for a real app:

  • Database: You need to be able to communicate from Dart to a database to store and retrieve resources.
  • Authentication: You need to be able to hide private data and only allow authorized users to update resources on the server.
  • Deployment: It’s nice to have the server running on your local machine, but it eventually needs to be accessible from the outside world.

And while not technically necessary, the following topics would also be helpful know:

  • Testing: If you aren’t testing your server code, you can’t be sure that making a change won’t break it.
  • Files: It’s probably not just strings from the database that you’ll want to return to clients. Eventually you’ll need to serve files as well.
  • Concurrency: If you have a server with more than one core, you might as well be using it. For that you’ll want to fire up your server on another isolate.
  • CI/CD: Manually uploading changes to your server code can get a little old after a while. It would be nice to set up a system that would automatically run tests and update the server whenever there are changes.

No promises, but I’d like to continue writing articles on these topics so that you can learn how to do them yourself without being dependent on a framework. But even if you do use a framework, knowing how things work makes you more productive.

Full code

Flutter Community

Articles and Stories from the Flutter Community

Suragch

Written by

Suragch

A Flutter and Dart developer. Follow me on Twitter @suragch1 to get updates of new articles.

Flutter Community

Articles and Stories from the Flutter Community

Suragch

Written by

Suragch

A Flutter and Dart developer. Follow me on Twitter @suragch1 to get updates of new articles.

Flutter Community

Articles and Stories from the Flutter Community

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store