Skip to content

Repository files navigation

Dart Express

A simple, thin expressjs inspired layer around Dart's primitive HttpServer APIs. Also included is a single static file server module.

This library will eventually expand to help with other common usage patterns and features as and when needed.

Add this to your package's pubspec.yaml file:

dependencies:
express: 0.1.6

Example Usages

basic jade and express app screenshot

import"package:jaded/jaded.dart";
import"package:express/express.dart";
import"dart:io";
import"views/jade.views.dart"as views;
import"public/jade.views.dart"as pages;
main(){
int counter =0;
var app =newExpress()
..use(newJadeViewEngine(views.JADE_TEMPLATES, pages:pages.JADE_TEMPLATES))
..use(newStaticFileHandler("public"))
..get('/', (ctx){
ctx.render('index', {'title':'Home'});
})
..get('/error', (ctx){
thrownewArgumentError("custom error in handler");
})
..get('/counter', (ctx){
ctx.sendJson({'counter': counter++});
});
app.listen("127.0.0.1", 8000);
}

Static files used by this app

  • /public
    • /stylesheets
      • style.css
    • layout.jade - layout for .jade pages called directly (i.e. no explicit route required)
    • layout-plain.jade - an alternative layout used by page.jade
    • static.jade - a static home page
    • page.jade - another page with layout-plain and inline :markdown content
    • links.md - a markdown partial
    • jade.yaml - tell express to watch and pre-compile .jade views in this directory
    • jade.views.dart - the auto-generated pre-compiled .jade views for this directory
  • /views
    • layout.jade
    • index.jade
    • links.md - a markdown partial
    • jade.yaml - tell express to watch and pre-compile .jade views in this directory
    • jade.views.dart - the auto-generated pre-compiled .jade views for this directory

Pre-compile .jade views on save

This example uses the Dart Editor build.dart Build System to compile all .jade views in any directory that contains an empty jade.yaml file.

To trigger this in your project add this to your projects /build.dart file:

import"package:express/express_build.dart"as express;
main(){
express.build();
}

Add middleware methods to a route

It happens that a developer has to check if a user is signed in or if the call to an API endpoint is valid. This is where middleware kicks in. In the following example, you can find a middleware method that checks if the user is signed in. The first line of the method should be replaced by the really checking if the user is signed in.

import'package:express/express.dart';
main(){
var app =newExpress()
..get('/', (ctx){
// render the homepage
ctx.sendJson({'homepage':true});
})
..get('/dashboard').then(isSignedIn).then((ctx){
// render the dashboard
ctx.sendJson({'dashboard':true});
});
app.listen("127.0.0.1", 8000);
}
boolisSignedIn(HttpContext ctx) {
bool signedIn =true;
if(!signedIn) {
ctx.res.redirect(newUri(path:'/'));
}
return signedIn;
}

As you can see, you can just chain the handlers with the then() method. The middlewares should return a boolean indicating if we should continue to the next method or if we should stop executing.

This is an example of an Redis-powered REST backend Backbones.js demo TODO application:

var client =newRedisClient();
var app =newExpress();
app
.use(newStaticFileHandler())
..get("/todos", (HttpContext ctx){
redis.keys("todo:*").then((keys) =>
redis.mget(keys).then(ctx.sendJson)
);
})
..get("/todos/:id", (HttpContext ctx){
var id = ctx.params["id"];
redis.get("todo:$id").then((todo) =>
todo !=null?
ctx.sendJson(todo) :
ctx.notFound("todo $id does not exist")
);
})
..post("/todos", (HttpContext ctx){
ctx.readAsJson().then((x){
redis.incr("ids:todo").then((newId){
var todo = $(x).defaults({"content":null,"done":false,"order":0});
todo["id"] = newId;
redis.set("todo:$newId", todo);
ctx.sendJson(todo);
});
});
})
..put("/todos/:id", (HttpContext ctx){
var id = ctx.params["id"];
ctx.readAsJson().then((todo){
redis.set("todo:$id", todo);
ctx.sendJson(todo);
});
})
..delete("/todos/:id", (HttpContext ctx){
redis.del("todo:${ctx.params['id']}");
ctx.send();
});
app.listen("127.0.0.1", 8000);

API

Register encapsulated Modules like StaticFileHandler

abstractclassModule {
voidregister(Express server);
}

The signature your Request Handlers should implement:

typedefvoidRequestHandler (HttpContext ctx);

The core Express API where all your Apps modules and request handlers are registered on. Then when the server has started, the request handler of the first matching route found will be executed.

abstractclassExpress {
factoryExpress() =_Express;
//Sets a config settingvoidconfig(String name, String value);
//Gets a config settingStringgetConfig(String name);
//Register a module to be used with this appExpressuse(Module module);
//Register a request handler that will be called for a matching GET requestRouteget(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching POST requestRoutepost(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching PUT requestRouteput(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching DELETE requestRoutedelete(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching PATCH requestRoutepatch(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching HEAD requestRoutehead(String atRoute, [RequestHandler handler]);
//Register a request handler that will be called for a matching OPTIONS requestRouteoptions(String atRoute, [RequestHandler handler]);
//Register a request handler that handles ANY verbRouteany(String atRoute, [RequestHandler handler]);
//Register a custom request handler. Execute requestHandler, if matcher is true.//If priority < 0, custom handler will be executed before route handlers, otherwise after. voidaddRequestHandler(boolmatcher(HttpRequest req), voidrequestHandler(HttpContext ctx), {int priority:0});
//Alias for registering a request handler matching ANY verbvoidoperator []=(String atRoute, RequestHandler handler);
//Can any of the registered routes handle this HttpRequestboolhandlesRequest(HttpRequest req);
// Return true if this HttpRequest is a match for this verb and routeboolisMatch(String verb, String route, HttpRequest req);
// When all routes and modules are registered - Start the HttpServer on host:portFuture<HttpServer> listen([String host, int port]);
//render a viewvoidrender(HttpContext ctx, String viewName, [dynamic viewModel]);
/// Permanently stops this [HttpServer] from listening for new connections. /// This closes this [Stream] of [HttpRequest]s with a done event.voidclose();
}

A high-level object encapsulating both HttpRequest and HttpResponse objects providing useful overloads for common operations and usage patterns.

abstractclassHttpContextimplementsHttpRequest {
//ContextString routePath;
HttpRequest req;
HttpResponse res;
Map<String,String> get params;
Map<String,String> get body;
//Read APIsStringget contentType;
Future<List<int>> readAsBytes();
Future<String> readAsText([CONV.Encoding encoding]);
Future<Object> readAsJson({CONV.Encoding encoding});
Future<Object> readAsObject([CONV.Encoding encoding]);
//Write APIsStringget responseContentType;
voidsetresponseContentType(String value);
HttpContexthead([int httpStatus, String statusReason, String contentType, Map<String,String> headers]);
HttpContextwrite(Object value, {String contentType});
HttpContextwriteText(String text);
HttpContextwriteBytes(List<int> bytes);
//Overloads for sending different content responses voidsend({Object value, String contentType, int httpStatus, String statusReason});
voidsendJson(Object value, {int httpStatus, String statusReason});
voidsendHtml(Object value, [int httpStatus, String statusReason]);
voidsendText(Object value, {String contentType, int httpStatus, String statusReason});
voidsendBytes(List<int> bytes, {String contentType, int httpStatus, String statusReason});
//Custom Status responsesvoidnotFound([String statusReason, Object value, String contentType]);
//Format response with the default renderervoidrender(String, [dynamic viewModel]);
//Close and mark this request as handled voidend();
//If the request has been handledboolget closed;
}

Modules

JadeViewEngine

Register the jaded view engine to render HTML .jade views. Supports both controller view pages and static page .jade templates.

app.use(newJadeViewEngine(views.JADE_TEMPLATES, pages:pages.JADE_TEMPLATES))

Usage

app.get('/', (HttpContext ctx){
ctx.render('index', {'title':'Home'});
})

Renders the /views/index.jade view with the {'title': 'Home'} view model.

A request without a matching route, e.g:

GET /page

Will execute the /public/page.jade template, passing these HTTP request vars as the viewModel:

Map viewModel = {
'method': req.method,
'uri': req.uri,
'headers': req.headers,
'request': req,
'response': req.response,
}

StaticFileHandler

Serve static files for requests that don't match any defined routes:

app.use(newStaticFileHandler('public'));

Serves static files from the /public folder.

Other APIs

// Register different FormattersabstractclassFormatterimplementsModule {
Stringget contentType;
Stringget format => contentType.split("/").last;
Stringrender(HttpContext ctx, dynamic viewModel, [String viewName]);
}
// The loglevel for expressint logLevel =LogLevel.Info;
// Inject your own logger, sensitive on logtypes (error, warning...)typedefLogger(Object obj, {int logtype});
Logger logger = (Object obj, {int logtype}) =>print(obj);

Contributors

About

A thin express-js inspired layer around Dart's primitive HttpServer APIs

Resources

Stars

125 stars

Watchers

9 watching

Forks

Releases

Packages

Used by

Contributors

Languages