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
; justnull
. - 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
- From Sublime’s menu, select Tools->Command Palette… and type in
install
. - Select Package Control: Install Package from the dropdown.
- 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. - From Sublime’s menu, select Preferences->Package Settings->Dart->Settings – User. This will open a settings file for the Dart plugin.
- Enter the following code into the settings file and save it, where
/path/to/dart-sdk
is the path to thedart-sdk
folder on your system.
{
"dart_sdk_path": "/path/to/dart-sdk"
}
Create a Dart Project
- From Sublime’s menu, select Tools->Command Palette… and type in
Dart:
. - Select Dart: Stagehand and then console-full to create a command-line application.
- 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.
Frequently Asked Questions (FAQs) about Custom Web Servers in Dart with Redstone
What is Dart and why is it used for web development?
Dart is a client-optimized programming language developed by Google for creating fast apps on any platform. It is used for web development due to its simplicity, efficiency, and scalability. Dart allows developers to write code once and run it on multiple platforms, reducing the time and effort required for coding. It also has a strong typing system and a robust set of libraries and frameworks, making it a powerful tool for web development.
How does Redstone enhance Dart’s capabilities for web server development?
Redstone is a server-side, metadata-driven microframework for Dart. It enhances Dart’s capabilities by providing a simple and intuitive way to build scalable and maintainable web applications. Redstone allows developers to use annotations to declare routes and inject dependencies, simplifying the process of web server development. It also supports middleware and has a plugin system, offering flexibility and extensibility.
What are the key differences between Dart and other web development languages?
Dart differs from other web development languages in several ways. It is a statically typed language, which means it checks types at compile-time, reducing runtime errors. Dart also supports both just-in-time (JIT) and ahead-of-time (AOT) compilation, allowing for faster development and efficient deployment. Furthermore, Dart has a rich standard library and a powerful framework called Flutter, which makes it a comprehensive solution for web, mobile, and desktop development.
How does Dart compare to other web frameworks like Aqueduct?
While both Dart and Aqueduct are powerful tools for web development, they have different focuses. Dart is a general-purpose programming language that can be used for web, mobile, and desktop development. On the other hand, Aqueduct is a web server framework specifically designed for Dart, offering features like request routing, OAuth 2.0, and a database ORM. Therefore, the choice between Dart and Aqueduct depends on the specific needs of the project.
What are some resources for learning more about Dart and Redstone?
There are many resources available for learning more about Dart and Redstone. The official Dart and Redstone documentation are great starting points. There are also numerous online tutorials, courses, and forums where you can learn from experts and peers. Websites like GitHub and Reddit have active Dart and Redstone communities where you can find code examples, discuss problems, and get advice.
Can I use Dart with other web frameworks?
Yes, Dart is compatible with a variety of web frameworks. Besides Redstone, Dart can also be used with frameworks like AngularDart and Flutter for web development. Each framework has its own strengths and is suited to different types of projects, so the choice of framework depends on your specific needs and preferences.
What are the prerequisites for using Dart and Redstone?
To use Dart and Redstone, you need to have a basic understanding of programming concepts and familiarity with object-oriented programming. Knowledge of web development basics like HTTP, REST, and JSON is also helpful. You can install Dart and Redstone on your computer using the Dart SDK and the Dart package manager, Pub.
How can I troubleshoot issues when using Dart and Redstone?
If you encounter issues when using Dart and Redstone, there are several ways to troubleshoot. You can refer to the official documentation, search for solutions online, or ask for help in Dart and Redstone communities. Debugging tools like Dart DevTools can also be useful for identifying and fixing issues.
What are some best practices for using Dart and Redstone?
Some best practices for using Dart and Redstone include writing clean and readable code, using strong typing to prevent errors, and following the DRY (Don’t Repeat Yourself) principle. It’s also important to write tests for your code, use version control, and keep your dependencies up to date.
How can I contribute to the Dart and Redstone communities?
There are many ways to contribute to the Dart and Redstone communities. You can contribute code, write documentation, report bugs, or help others by answering questions. You can also share your own projects and experiences, participate in discussions, and give feedback on proposed changes.
![Monty Rasmussen](https://uploads.sitepoint.com/wp-content/uploads/2015/06/1434569560montyr75_125x125-96x96.jpg)
I'm a senior-level Web application developer with 20+ years programming experience. I have a passion for technology, especially the Web as an application platform. As a self-appointed Dart evangelist, I'm counting on it and other new Web technologies to bring the performance and productivity of desktop tools to the Web.