JavaScript
Article

Easy Custom Web Servers with Dart and Redstone

By Monty Rasmussen

Using Node.js to create server-side scripts is all the rage right now, and with good reason. It’s fast, event-driven, and perhaps best of all for web developers, it’s powered by JavaScript. If your front end code is all JavaScript, the benefits of using the same language on the back end are clear. Node even has great server-side frameworks like Express that make creating custom Web servers fast and easy.

But is there a better way?

What Is Dart?

Dart is an open-source, scalable, object-oriented programming language, with robust libraries and runtimes, for building web, server, and mobile apps. It was originally developed by Lars Bak and Kasper Lund for Google, but has since become an ECMA standard.

You can get all of Node’s benefits plus a few more when you use Dart and the Redstone framework on the server side. As an added bonus, you leave behind JavaScript’s quirks. Like Node, the Dart virtual machine is event-driven, asynchronous, and allows you to build a client and server app in one language and share code between them. There isn’t space here to go over all of Dart’s advantages over JavaScript (another article, maybe), but if you’re interested in more details, follow some of the links below.

Advantages of Dart

  • Immutable objects and simpler semantics, allowing for better code optimization (more speed) in the virtual machine.
  • Optional types and support for finals and constants.
  • Support for optional positional or named function parameters with default values.
  • Lexical scope for variables, closures, and this.
  • No variable hoisting.
  • No type coercion in assignments or comparisons.
  • Futures (promises) and Streams.
  • No undefined; just null.
  • Only true is truthy.
  • Comprehensive standard libraries.
  • Syntactic sugar to reduce verbosity in class constructors.
  • Built-in support for code modules, with support for deferred loading.
  • Dart has its own advanced code profiler, Observatory.
  • Watch Moving from Node.js to Dart for a look at one developer’s experience.

That list just scratches the surface. Check out the online book Dart: Up and Running for a crash course in the language. If you know JavaScript, Java, PHP, ActionScript, C/C++, or another “curly brace” language, you’ll find Dart to be familiar, and you can be productive with Dart within an hour or so.

Get Dart

There are many editors that support Dart development, and the Dart team has announced that JetBrains WebStorm will be the preferred editor going forward, but to keep things simple (and free), we’ll be using the popular Sublime Text 3 with a Dart plugin for this tutorial. Even though it’s technically still in beta, it is the recommended version to use.

Download Software

You will need a few pieces of software to complete this tutorial.

Sublime Text 3

If you don’t already have Sublime Text 3, download and install the version appropriate for your operating system. The latest build as of this writing is 3083.

Dart SDK

Download the correct Dart SDK for your system. Note that for this tutorial, you will not need the editor (now deprecated) or Dartium (a special build of Chromium with an embedded Dart VM).

Unzip the Dart SDK and place the dart-sdk folder anywhere on your system. On Windows, I prefer C:/Program Files/dart/dart-sdk.

Configure Sublime Text 3

Run Sublime Text 3. You’ll need to configure the editor to support Dart.

Package Control

If you haven’t already installed Package Control, follow these instructions to install it now. Note that you will need to restart Sublime Text 3 once the installation is complete.

Dart Plugin

  1. From Sublime’s menu, select Tools->Command Palette… and type in install.
  2. Select Package Control: Install Package from the dropdown.
  3. Type dart and select the Dart package. Note that you may need to restart Sublime before all of the plugin’s features will be available.
  4. From Sublime’s menu, select Preferences->Package Settings->Dart->Settings – User. This will open a settings file for the Dart plugin.
  5. Enter the following code into the settings file and save it, where /path/to/dart-sdk is the path to the dart-sdk folder on your system.
{ 
  "dart_sdk_path": "/path/to/dart-sdk" 
}

Create a Dart Project

  1. From Sublime’s menu, select Tools->Command Palette… and type in Dart:.
  2. Select Dart: Stagehand and then console-full to create a command-line application.
  3. At the bottom of the Sublime window, enter the path where you would like Dart’s Stagehand tool to create your new Dart project. Note that the target directory must be either new or empty. I recommend naming it something like redstone_intro.

Note: if during the above process, you see an error that Stagehand is not enabled, you need to do the following from a terminal:

cd /path/to/dart-sdk/bin
pub global activate stagehand

Acquire Dependencies

With your new project created, open up the file pubspec.yaml. Dart uses your pubspec file to manage your project’s dependencies. Replace the pre-generated dependencies section in pubspec.yaml with one that looks like this (remove any # characters, which indicate a comment):

dependencies:
  redstone: '>=0.5.21 <0.6.0'

Save the file. Sublime will automatically instruct Dart’s package manager, called Pub, to acquire all necessary dependencies, including the Redstone framework. Pub will only get Redstone versions in the specified range. You can also cause Sublime to get your dependencies with the hotkey F7 while you’re editing pubspec.yaml.

For more information and examples for Redstone, see the project’s Github wiki.

Create a Web Server

Setting up a simple server with Redstone is easy. Open the main.dart file and remove all of the pre-generated code. Insert the following code in its place.

import 'package:redstone/server.dart' as Server;

void main() {
  Server.setupConsoleLog();
  Server.start();
}

Since this may be your first Dart program, let’s analyze this code line by line. Developers familiar with Java, JavaScript, C#, or similar languages will find most of these concepts instantly familiar.

import 'package:redstone/server.dart' as Server;

First, you tell the Dart analyzer that you will be using code from Redstone’s server.dart. The special package: prefix indicates that this code is an external dependency acquired by Pub. (If you like, you can examine this and all other downloaded packages by exploring the contents of the packages folder in your project.) This imports Redstone’s classes and top-level functions into your Dart program’s namespace. Since it includes functions with common names like start(), you contain the imported code within a custom namespace called Server with the syntax as Server.

void main()

All Dart programs start execution with the top-level main() function. Dart allows you to optionally specify types for variables and function return values, and void indicates that main() will return nothing.

Server.setupConsoleLog();

You imported the Redstone package under the alias Server, so you must use that reference when calling its functions. This call isn’t strictly necessary, but it’s helpful during development. It sets up console logging for the Redstone framework, so informative messages will appear in the console as Redstone’s code executes.

Server.start();

This line calls Redstone’s start() function, which starts up the web server. By default, it listens for requests on 0.0.0.0:8080 (current IP on port 8080), though this is configurable.

That’s it! Your server doesn’t yet respond in any meaningful way to requests, but it is listening. Run the code in main.dart with the hotkey Shift+F7. Console output will appear in Sublime’s output panel, which displays by default in the lower portion of the Sublime interface.

INFO: <current date/time>: Running on 0.0.0.0:8080

You can stop the running application using the hotkey Ctrl+Keypad0 (that’s Ctrl and the zero key on your keypad).

Note: You can also start/stop the server via the terminal:

cd /path/to/dart-sdk/bin
./dart /path/to/redstone_intro/bin/main.dart

To access all of the Dart file commands through Sublime’s command palette (necessary if you don’t have a keypad), select Tools->Command Palette… from the menu and type Dart:, then select the command you need. The keyboard shortcut for that is Ctrl+., Ctrl+. (hold down Ctrl and tap the period twice).

For more handy keyboard shortcuts, refer to the Dart plugin’s Shortcuts page.

Path Segment Parameters

Now lets make the server respond to a few requests. You can use Redstone’s Route annotation to set up a handler.

Hello

Add the following code to the end of main.dart (after the main() function).

@Server.Route("/hello")
String hello() {
  print("User soliciting greeting...");
  return "Hello, Browser!";
}

Note that you still need to include the reference to Server in the annotation, because that’s the alias you applied to Redstone when you imported it. The annotation (beginning with @) tells Redstone’s router to respond with the return value of the hello() function whenever receiving a request in the form of:

http://localhost:8080/hello

If your Dart server script is still running, stop and restart it, then open a browser and navigate to that URL to see the server in action. You should see the string “Hello, Browser!” appear. Also, the call to print() will output a helpful message to the system console.

Hi

Append another Route block to the end of main.dart.

@Server.Route("/hi")
String hi() => "Hi, Browser!";

This code is very similar to the prior example, but it makes use of Dart’s fat arrow syntax for defining a very short function. Written this way, the hi() function will return the result of one expression following the arrow, in this case just a string literal.

To test this example in your browser, use

http://localhost:8080/hi

Advanced Path Segment Parameters

Acknowledging static parameters is all well and good, but in the real world, you often need to pass dynamic values to the server in order to receive a customized response.

Mock Data

For the next few exercises, you’ll need to add a data model that will serve as a mock database, as well as a few helper functions.

Above main(), but below your import statement, add a list of users.

import 'package:redstone/server.dart' as Server;

List<Map> users = [
  {"id": "1", "username": "User1", "password": "123456", "type": "manager"},
  {"id": "2", "username": "User2", "password": "password", "type": "programmer"},
  {"id": "3", "username": "User3", "password": "12345", "type": "programmer"},
  {"id": "4", "username": "User4", "password": "qwerty", "type": "secretary"},
  {"id": "5", "username": "User5", "password": "123456789", "type": "secretary"}
];

void main() {
  Server.setupConsoleLog();
  Server.start();
}

In Dart, a List is essentially an array and a Map works like a standard JavaScript object (or a dictionary, or hashmap from a statically-typed language). The variable users is defined to be a List of Map elements with the List<Map> syntax. The literal syntax using square brackets and curly braces should be familiar to JavaScript programmers. Defining users above main() makes it a top-level variable, accessible to all the functions in the file.

Helper Functions

Now that you have a list of users to query, it’s time to define a couple of helper functions to format the server’s responses. Add these to the end of main.dart.

Map success(String messageType, payload) {
  return {
    "messageType": messageType,
    "payload": payload
  };
}

Map error(String errorMessage) {
  print(errorMessage);

  return {
    "messageType": "error",
    "error": errorMessage
  };
}

The first function, success(), returns a Map, which it constructs from its two parameters. messageType is a string that will be “user” or “users”, depending on whether the server is responding with one user or a list of users. The payload parameter is deliberately left untyped, so as to be flexible. The default type of dynamic is applied by the Dart language.

The error() function does essentially the same thing, but the returned Map is filled with values appropriate to an error condition.

When one of the handlers returns a Map instead of a simple string, the Redstone framework automatically serializes it to JSON on its way out.

Get User by ID

Now you’re ready to add another route handler to main.dart.

@Server.Route("/user/id/:id")
Map getUserByID(String id) {
  print("Searching for user with ID: $id");

  // convert the ID from String to int
  int index = int.parse(id, onError: (_) => null);

  // check for error
  if (index == null || index < 1 || index > users.length) {
    return error("Invalid ID");
  }

  // get user
  Map foundUser = users[index - 1];

  // return user
  return success("user", foundUser);
}

The route is configured to accept two static parameters (user and id) and one dynamic parameter (:id). The colon syntax indicates that the handler will expect a user-provided value. This function’s code is deliberately verbose and heavily commented for clarity.

print("Searching for user with ID: $id");

First, a message is printed to the server’s console. The $id syntax makes use of Dart’s built-in string interpolation feature (more on that later).

int index = int.parse(id, onError: (_) => null);

Next, you convert the incoming id from a string to an integer for use as a List index. int.parse() takes the value to be converted and, optionally, a callback function for dealing with any parsing errors. onError is a named parameter, and the callback is a fat arrow function that returns null. The callback takes one parameter, but since it isn’t used, by convention it has the alias _ and is ignored. In the event that id cannot be parsed into a valid integer, index will be assigned the return value of the onError function, which in this case is null.

if (index == null || index < 1 || index > users.length) {
  return error("Invalid ID");
}

If index ends up being invalid or out of range, this code returns an error object with the message “Invalid ID”, using the error() helper function.

Map foundUser = users[index - 1];
return success("user", foundUser);

If all is well, your handler looks up and returns the requested user to the caller. The success() helper function constructs the message Map for you with type “user”. The payload is a Map object containing the user’s data.

As a test, direct your browser to the following URL:

http://localhost:8080/user/id/5

The result will be a JSON-encoded string containing the requested user data.

Get User by Type

Add another handler to your main.dart file.

@Server.Route("/user/type/:type")
Map getUsersByType(String type) {
  print("Searching for users with type: $type");

  // find qualifying users
  List<Map> foundUsers = users.where((Map user) => user['type'] == type).toList();

  // check for error
  if (foundUsers.isEmpty) {
    return error("Invalid type");
  }

  // return list of users
  return success("users", foundUsers);
}

This route will allow users to be queried by type rather than id. Since there may be more than one user of a given type, you’ll need to be prepared to return multiple users if necessary.

To construct a List of user Map objects matching a particular user type, use the where() function, which is a standard part of any List object. You pass it a function that conducts a test for retention on each element and returns true if the element it’s examining passes. where() actually returns an Iterable, an ancestor of List, so you convert it to the required List with the toList() function. If no users of type are found, foundUsers will be an empty List, in which case the server returns an error object.

Test the new route with an appropriate URL. The response object will contain a JSON array with two user elements:

http://localhost:8080/user/type/programmer

Query Parameters

It’s similarly easy to use a query string and key/value pairs to get what you need from Redstone.

Add this route handler to main.dart.

@Server.Route("/user/param")
Map getUserByIDParam(@Server.QueryParam("id") String userID) {
  return getUserByID(userID);
}

This time you need to annotate the handler’s parameter, userID, causing it to be filled with the value of a query parameter called id.

http://localhost:8080/user/param?id=2

Serving Static Pages

What if you want your Dart server to dish out static pages? With just a few more lines of code, you can have that too.

First, create a folder called web as a sibling to your project’s bin folder. Inside the new folder, create an HTML file called index.html, using the following code.

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>

  <body>
    <p>Hello from index.html!</p>
  </body>
</html>

You need a few more packages from Pub in order to make this smooth. Open your pubspec.yaml file again and make the dependencies section look like this:

dependencies:
  redstone: '>=0.5.21 <0.6.0'
  shelf_static: '>=0.2.2 <0.3.0'
  path: '>=1.3.5 <1.4.0'

Redstone is built on top of Shelf, which is a lower-level server library built and maintained by the Dart team at Google. This allows you to use any Shelf middleware to add functionality to a Redstone server. You also bring in Path to help you parse and manipulate path strings.

Sublime should automatically use Pub to acquire the new dependencies when you save pubspec.yaml.

Once those packages have been downloaded into your project, add these import statements at the top of main.dart.

import 'dart:io' show Platform;
import "package:path/path.dart" as Path;
import 'package:shelf_static/shelf_static.dart';

You import one of the Dart core libraries, io, to get access to the Platform class. The show keyword lets you import only Platform, leaving all other I/O functions and classes out of the program.

Because the Path library has top-level functions with common names, it’s best to alias that import as Path.

Add two new lines to the beginning of main().

void main() {
  String pathToWeb = Path.normalize(
    "${Path.dirname(Path.fromUri(Platform.script))}/../web"
  );
  Server.setShelfHandler(
    createStaticHandler(pathToWeb, defaultDocument: "index.html")
  );
  Server.setupConsoleLog();
  Server.start();
}

You can test that index.html gets served by restarting the Dart server application and navigating to the server’s root.

http://localhost:8080/

I’ll leave it as an exercise for the reader to research Shelf and Path, but we should briefly discuss one of Dart’s more useful features here: string interpolation. You can place the value of an expression into a string by using ${}. If the expression is just an identifier, you only need the $.

int myNumber = 5;

// 5 is my favorite number
String str1 = "$myNumber is my favorite number.";

// 5 + 10 = 15
String str2 = "$myNumber + 10 = ${myNumber + 10}";

Conclusion

In this tutorial, I introduced a fantastic alternative to JavaScript, Node, and Express on the server side. Dart is a faster, modern language built to scale to millions of lines of code. Redstone is just one of many frameworks for the server that make your life as a developer easier, but it’s among my favorites because it makes great use of Dart’s code annotation features to reduce the amount of boilerplate required to set up complex server interactions.

If you write your client-side code with Dart too, you can share code between client and server, and you benefit from avoiding costly context switches when your code bases are built with different languages. During development, you can use the special Dartium browser, enabling the quick change-and-refresh workflow JavaScript developers have enjoyed for years. When all your client-side code is ready, with a few clicks (or command line entries), dart2js will compile your Dart code to JavaScript for all modern browsers, minified, concatenated, tree-shaken, and ready to deploy.

Join the Dart side.

  • Günter Zöchbauer

    I absolutely agree with the author.
    Dart and Redstone can make your life as a developer easier.

  • Antonino Porcino

    great article!

  • http://qiita.com/takyam takyam(たくやむ)

    Thank you for a great article.
    May I translate this article in Japanese to publish on my blog ( qiita.com/takyam )?

  • OphelieLechat

    Hi Takyam! That shouldn’t be a problem. Can you send me an email at ophelie@sitepoint.com?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.