To bind a function with an URL, just use the @Route
annotation
@app.Route("/")
helloWorld() => "Hello, World!";
Redstone.dart will serialize the returned value for you. So, if your function returns a List
or a Map
, the client receives a JSON object:
@app.Route("/user/find/:id")
getUser(String id) => {"name": "User", "login": "user"};
If your function depends on async operations, you can also return a Future
@app.Route("/service")
service() => doSomeAsyncOperation().then((_) => {"success": true});
You can easily bind path segments and query parameters
@app.Route("/user/find/:type")
findUsers(String type, @app.QueryParam() String name) {
// ...
}
You can also bind the request body
@app.Route("/user/add", methods: const [app.POST])
addUser(@app.Body(app.JSON) Map user) {
// ...
}
It’s also possible to access the current request object
@app.Route("/service", methods: const [app.GET, app.POST])
service() {
if (app.request.method == app.GET) {
// ...
} else if (app.request.method == app.POST) {
if (app.request.bodyType == app.JSON) {
var json = app.request.body;
// ...
} else {
// ...
}
}
};
Interceptors are useful when you need to apply a common behavior to a group of targets (functions or static content). For example, you can create an interceptor to apply a security constraint or to manage a resource
@app.Interceptor(r'/admin/.*')
adminFilter() {
if (app.request.session["username"] != null) {
return app.chain.next();
} else {
return app.chain.abort(HttpStatus.UNAUTHORIZED);
//or app.chain.redirect("/login.html");
}
}
@app.Interceptor(r'/services/.+')
dbConnInterceptor() async {
var conn = new DbConn();
app.request.attributes["dbConn"] = conn;
var response = await app.chain.next();
await conn.close()
return response;
}
@app.Route('/services/find')
find(@app.Attr() dbConn) {
// ...
}
Use the @ErrorHandler
annotation to register error handlers.
@app.ErrorHandler(404)
handleNotFoundError() => app.redirect("/error/not_found.html");
@app.ErrorHandler(500)
handleServerError() {
print(app.chain.error);
return new shelf.Response.internalServerError(body: "Server Error.");
}
You can use classes to group routes, interceptors and error handlers
@Group("/user")
class UserService {
@app.Route("/find")
findUser(@app.QueryParam("n") String name,
@app.QueryParam("c") String city) {
// ...
}
@app.Route("/add", methods: const [app.POST])
addUser(@app.Body(app.JSON) Map json) {
// ...
}
}
Register one or more modules before calling app.start()
import 'package:redstone/redstone.dart' as app;
import 'package:di/di.dart';
main() {
app.addModule(new Module()
..bind(ClassA)
..bind(ClassB));
app.setupConsoleLog();
app.start();
}
Routes, interceptors, error handlers and groups can require dependencies
@app.Route('/service')
service(@app.Inject() ClassA objA) {
// ...
}
@app.Interceptor(r'/services/.+')
interceptor(ClassA objA, ClassB objB) {
// ...
}
@app.ErrorHandler(404)
notFound(ClassB objB) {
// ...
}
@app.Group('/group')
class Group {
ClassA objA;
Group(ClassA this.objA);
@app.Route('/service')
service() {
// ...
}
}
You can easily create mock requests to test your server
library services;
import 'package:redstone/redstone.dart' as app;
@app.Route("/user/:username")
helloUser(String username) => "hello, $username";
import 'package:test/test.dart';
import 'package:redstone/redstone.dart' as app;
import 'package:your_package_name/services.dart';
main() {
// Load handlers in 'services' library
setUp(() => app.redstoneSetUp([#services]));
// Remove all loaded handlers
tearDown(() => app.redstoneTearDown());
test("hello service", () {
// Create a mock request
var req = new app.MockRequest("/user/luiz");
// Dispatch the request
return app.dispatch(req).then((resp) {
// Verify the response
expect(resp.statusCode, equals(200));
expect(resp.mockContent, equals("hello, luiz"));
});
});
}