mirror of https://gitee.com/bigwinds/arangodb
1425 lines
39 KiB
Plaintext
1425 lines
39 KiB
Plaintext
!CHAPTER Details on Controller
|
|
|
|
!SUBSECTION Create
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`new Controller(applicationContext, options)`
|
|
|
|
This creates a new Controller. The first argument is the controller
|
|
context available in the variable *applicationContext*. The second one is an
|
|
options array with the following attributes:
|
|
|
|
* *urlPrefix*: All routes you define within will be prefixed with it.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app = new Controller(applicationContext, {
|
|
urlPrefix: "/meadow"
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SECTION HTTP Methods
|
|
|
|
!SUBSECTION get
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.get(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `get`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.get('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION head
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.head(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `head`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.head('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION post
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.post(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `post`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.post('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION put
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.put(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `put`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.put('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION patch
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.patch(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `patch`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.patch('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION delete
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.delete(path, callback)`
|
|
|
|
Defines a new route on `path` that handles requests from the HTTP verb `delete`.
|
|
This route can also be 'parameterized' like `/goose/:barn`.
|
|
In this case you can later get the value the user provided for `barn`
|
|
via the `params` function in the `request`.
|
|
The function defined in `callback` will be invoked whenever this type of
|
|
request is recieved.
|
|
`callback` get's two arguments `request` and `response`, see below for further
|
|
information about these objects.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.delete('/goose/barn', function (req, res) {
|
|
// Take this request and deal with it!
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SECTION Documenting and constraining a specific route
|
|
|
|
If you now want to document your route, you can use JSDoc style comments (a
|
|
multi-line comment block where the first line starts with */*** instead
|
|
of */**) above your routes to do that:
|
|
|
|
```js
|
|
/** Get all foxxes
|
|
*
|
|
* If you want to get all foxxes, please use this
|
|
* method to do that.
|
|
*/
|
|
app.get("/foxxes", function () {
|
|
// ...
|
|
});
|
|
```
|
|
|
|
The first line will be treated as a summary (For optical reasons in the
|
|
produced documentation, the summary is restricted to 60 characters). All
|
|
following lines will be treated as additional notes shown in the detailed
|
|
view of the route documentation. With the provided information, Foxx will
|
|
generate a nice documentation for you. Furthermore you can describe your
|
|
API by chaining the following methods onto your path definition:
|
|
|
|
!SUBSECTION pathParam
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.pathParam(id, options)`
|
|
|
|
If you defined a route "/foxx/:name", containing a parameter called `name` you can
|
|
constrain which format this parameter is allowed to have.
|
|
This format is defined using *joi* in the `options` parameter.
|
|
Using this function will at first allow you to access this parameter in your
|
|
route handler using `req.params(id)`, will reject any request having a paramter
|
|
that does not match the *joi* definition and creates a documentation for this
|
|
parameter in ArangoDBs WebInterface.
|
|
|
|
For more information on *joi* see [the official Joi documentation](https://github.com/spumko/joi).
|
|
|
|
*Parameter*
|
|
|
|
* *id*: name of the param.
|
|
* *options*: a joi schema or an object with the following properties:
|
|
* *type*: a joi schema.
|
|
* *description*: documentation description for the parameter.
|
|
* *required* (optional): whether the parameter is required. Default: determined by *type*.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.get("/foxx/:name", function {
|
|
// Do something
|
|
}).pathParam("name", joi.string().required().description("Name of the Foxx"));
|
|
```
|
|
|
|
You can also pass in a configuration object instead:
|
|
|
|
```js
|
|
app.get("/foxx/:name", function {
|
|
// Do something
|
|
}).pathParam("name", {
|
|
type: joi.string(),
|
|
required: true,
|
|
description: "Name of the Foxx"
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION queryParam
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.queryParam(id, options)`
|
|
|
|
Describe a query parameter:
|
|
|
|
If you defined a route "/foxx", you can allow a query paramter with the
|
|
name `id` on it and constrain the format of this parameter by giving it a *joi* type in the `options` parameter.
|
|
Using this function will at first allow you to access this parameter in your
|
|
route handler using `req.params(id)`, will reject any request having a paramter
|
|
that does not match the *joi* definition and creates a documentation for this
|
|
parameter in ArangoDBs WebInterface.
|
|
|
|
For more information on *joi* see [the official Joi documentation](https://github.com/spumko/joi).
|
|
|
|
You can also provide a description of this parameter and
|
|
whether you can provide the parameter multiple times.
|
|
|
|
*Parameter*
|
|
|
|
* *id*: name of the parameter
|
|
* *options*: a joi schema or an object with the following properties:
|
|
* *type*: a joi schema
|
|
* *description*: documentation description for this param.
|
|
* *required* (optional): whether the param is required. Default: determined by *type*.
|
|
* *allowMultiple* (optional): whether the param can be specified more than once. Default: `false`.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
}).queryParam("id",
|
|
joi.string()
|
|
.required()
|
|
.description("Id of the Foxx")
|
|
.meta({allowMultiple: false})
|
|
});
|
|
```
|
|
|
|
You can also pass in a configuration object instead:
|
|
|
|
```js
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
}).queryParam("id", {
|
|
type: joi.string().required().description("Id of the Foxx"),
|
|
allowMultiple: false
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION bodyParam
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.bodyParam(paramName, options)`
|
|
|
|
Defines that this route expects a JSON body when requested and binds it to
|
|
a pseudo parameter with the name `paramName`.
|
|
The body can than be read in the the handler using `req.params(paramName)` on the request object.
|
|
In the `options` parameter you can define how a valid request body should look like.
|
|
This definition can be done in two ways, either using *joi* directly.
|
|
Accessing the body in this case will give you a JSON object.
|
|
The other way is to use a Foxx *Model*.
|
|
Accessing the body in this case will give you an instance of this Model.
|
|
For both ways an entry for the body will be added in the Documentation in ArangoDBs WebInterface.
|
|
For information about how to annotate your models, see the Model section.
|
|
All requests sending a body that does not match the validation given this way
|
|
will automatically be rejected.
|
|
|
|
You can also wrap the definition into an array, in this case this route
|
|
expects a body of type array containing arbitrary many valid objects.
|
|
Accessing the body parameter will then of course return an array of objects.
|
|
|
|
Note: The behavior of `bodyParam` changes depending on the `rootElement` option
|
|
set in the [manifest](../Develop/Manifest.md). If it is set to `true`, it is
|
|
expected that the body is an
|
|
object with a key of the same name as the `paramName` argument.
|
|
The value of this object is either a single object or in the case of a multi
|
|
element an array of objects.
|
|
|
|
*Parameter*
|
|
|
|
* *paramName*: name of the body parameter in `req.parameters`.
|
|
* *options*: a joi schema or an object with the following properties:
|
|
* *description*: the documentation description of the request body.
|
|
* *type*: the Foxx model or joi schema to use.
|
|
* *allowInvalid* (optional): `true` if validation should be skipped. (Default: `false`)
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.post("/foxx", function (req, res) {
|
|
var foxxBody = req.parameters.foxxBody;
|
|
// Do something with foxxBody
|
|
}).bodyParam("foxxBody", {
|
|
description: "Body of the Foxx",
|
|
type: FoxxBodyModel
|
|
});
|
|
```
|
|
|
|
Using a joi schema:
|
|
|
|
```js
|
|
app.post("/foxx", function (req, res) {
|
|
var joiBody = req.parameters.joiBody;
|
|
// Do something with the number
|
|
}).bodyParam("joiBody", {
|
|
type: joi.number().integer().min(5),
|
|
description: "A number greater than five",
|
|
allowInvalid: false // default
|
|
});
|
|
```
|
|
|
|
Shorthand version:
|
|
|
|
```js
|
|
app.post("/foxx", function (req, res) {
|
|
var joiBody = req.parameters.joiBody;
|
|
// Do something with the number
|
|
}).bodyParam(
|
|
"joiBody",
|
|
joi.number().integer().min(5)
|
|
.description("A number greater than five")
|
|
.meta({allowInvalid: false}) // default
|
|
);
|
|
```
|
|
|
|
|
|
!SUBSECTION errorResponse
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.errorResponse(errorClassOrName, code, description, [callback])`
|
|
|
|
Define a reaction to a thrown error for this route: If your handler throws an error
|
|
of the errorClass defined in `errorClassOrName` or the error has an attribute `name` equal to `errorClassOrName`,
|
|
it will be caught and the response object will be filled with the given
|
|
status code and a JSON with error set to your description as the body.
|
|
|
|
If you want more control over the returned JSON, you can give an optional fourth
|
|
parameter in form of a function. It gets the error as an argument, the return
|
|
value will be transformed into JSON and then be used as the body.
|
|
The status code will be used as described above. The description will be used for
|
|
the documentation.
|
|
|
|
It also adds documentation for this error response to the generated documentation.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
/* define our own error type, FoxxyError */
|
|
var FoxxyError = function (message) {
|
|
this.name = "FError";
|
|
this.message = "the following FoxxyError occurred: " + message;
|
|
};
|
|
FoxxyError.prototype = new Error();
|
|
|
|
app.get("/foxx", function {
|
|
/* throws a FoxxyError */
|
|
throw new FoxxyError();
|
|
}).errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
|
|
|
|
app.get("/foxx", function {
|
|
throw new FoxxyError("oops!");
|
|
}).errorResponse("FError", 303, "This went completely wrong. Sorry!", function (e) {
|
|
return {
|
|
code: 123,
|
|
desc: e.message
|
|
};
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION onlyif
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.onlyIf(check)`
|
|
|
|
This functionality is used to secure a route by applying a checking function
|
|
on the request beforehand, for example the check authorization.
|
|
It expects `check` to be a function that takes the request object as first parameter.
|
|
This function is executed before the actual handler is invoked.
|
|
If `check` throws an error the actual handler will not be invoked.
|
|
Remember to provide an `errorResponse` on the route as well to define the behavior in this case.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
}).onlyIf(aFunction).errorResponse(ErrorClass, 303, "This went completely wrong. Sorry!");
|
|
```
|
|
|
|
|
|
!SUBSECTION onlyIfAuthenticated
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`FoxxController#onlyIfAuthenticated(code, reason)`
|
|
|
|
Please activate sessions for this app if you want to use this function.
|
|
Or activate authentication (deprecated).
|
|
If the user is logged in, it will do nothing. Otherwise it will respond with
|
|
the status code and the reason you provided (the route handler won't be called).
|
|
This will also add the according documentation for this route.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
}).onlyIfAuthenticated(401, "You need to be authenticated");
|
|
```
|
|
|
|
|
|
!SUBSECTION summary
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.summary(description)`
|
|
|
|
Set the summary for this route in the documentation.
|
|
Can't be longer than 8192 characters.
|
|
This is equal to using JavaDoc style comments right above your function.
|
|
If you provide both comment and `summary()` the call to `summary()` wins
|
|
and will be used.
|
|
|
|
*Examples*
|
|
|
|
Version with comment:
|
|
|
|
```js
|
|
/** Short description
|
|
*
|
|
* Longer description
|
|
* with multiple lines
|
|
*/
|
|
app.get("/foxx", function() {
|
|
});
|
|
```
|
|
|
|
is identical to:
|
|
|
|
```js
|
|
app.get("/foxx", function() {
|
|
})
|
|
.summary("Short description")
|
|
.notes(["Longer description", "with multiple lines"]);
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION notes
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Route.notes(...description)`
|
|
|
|
Set the long description for this route in the documentation
|
|
|
|
*Examples*
|
|
|
|
Version with comment:
|
|
|
|
```js
|
|
/** Short description
|
|
*
|
|
* Longer description
|
|
* with multiple lines
|
|
*/
|
|
app.get("/foxx", function() {
|
|
});
|
|
```
|
|
|
|
is identical to:
|
|
|
|
```js
|
|
app.get("/foxx", function() {
|
|
})
|
|
.summary("Short description")
|
|
.notes(["Longer description", "with multiple lines"]);
|
|
```
|
|
|
|
|
|
|
|
|
|
!SUBSECTION extend
|
|
|
|
In many use-cases several of the functions are always used in a certain combination (e.g.: `onlyIf` with `errorResponse`).
|
|
In order to avoid duplicating this equal usage for several routes in your application you can
|
|
extend the controller with your own functions.
|
|
These functions can simply combine several of the above on a single name, so you only have to
|
|
invoke your self defined single function on all routes using these extensions.
|
|
|
|
|
|
|
|
|
|
`Controller.extend(extensions)`
|
|
|
|
Extends all functions to define routes in this controller.
|
|
This allows to combine several route extensions with the invocation
|
|
of a single function.
|
|
This is especially useful if you use the same input parameter in several routes of
|
|
your controller and want to apply the same validation, documentation and error handling
|
|
for it.
|
|
|
|
The `extensions` parameter is a JSON object with arbitrary keys.
|
|
Each key is used as the name of the function you want to define (you cannot overwrite
|
|
internal functions like `pathParam`) and the value is a function that will be invoked.
|
|
This function can get arbitrary many arguments and the `this` of the function is bound
|
|
to the route definition object (e.g. you can use `this.pathParam()`).
|
|
Your newly defined function is chainable similar to the internal functions.
|
|
|
|
**Examples**
|
|
|
|
Define a validator for a queryParameter, including documentation and errorResponses
|
|
in a single command:
|
|
|
|
```js
|
|
controller.extend({
|
|
myParam: function (maxValue) {
|
|
this.queryParam("value", {type: joi.number().required()});
|
|
this.onlyIf(function(req) {
|
|
var v = req.param("value");
|
|
if (v > maxValue) {
|
|
throw new NumberTooLargeError();
|
|
}
|
|
});
|
|
this.errorResponse(NumberTooLargeError, 400, "The given value is too large");
|
|
}
|
|
});
|
|
|
|
controller.get("/goose/barn", function(req, res) {
|
|
// Will only be invoked if the request has parameter value and it is less or equal 5.
|
|
}).myParam(5);
|
|
```
|
|
|
|
|
|
|
|
!SECTION Documenting and constraining all routes
|
|
|
|
In addition to documenting a specific route, you can also
|
|
do the same for all routes of a controller. For this purpose
|
|
use the **allRoutes** object of the according controller.
|
|
The following methods are available.
|
|
|
|
**Examples**
|
|
|
|
Provide an error response for all routes handled by this controller:
|
|
|
|
```js
|
|
ctrl.allRoutes
|
|
.errorResponse(Unauthorized, 401, 'Not authenticated.')
|
|
.errorResponse(NotFound, 404, 'Document not found.')
|
|
.errorResponse(ImATeapot, 418, 'I\'m a teapot.');
|
|
|
|
ctrl.get('/some/route', function (req, res) {
|
|
// ...
|
|
throw new NotFound('The document does not exist');
|
|
// ...
|
|
}); // no errorResponse needed here
|
|
|
|
ctrl.get('/another/route', function (req, res) {
|
|
// ...
|
|
throw new NotFound('I made you a cookie but I ated it');
|
|
// ...
|
|
}); // no errorResponse needed here either
|
|
```
|
|
|
|
!SUBSECTION errorResponse
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Controller.allRoutes.errorResponse(errorClass, code, description)`
|
|
|
|
This is equal to invoking `Route.errorResponse` on all routes bound to this controller.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.allRoutes.errorResponse(FoxxyError, 303, "This went completely wrong. Sorry!");
|
|
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION onlyIf
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Controller.allRoutes.onlyIf(code, reason)`
|
|
|
|
This is equal to invoking `Route.onlyIf` on all routes bound to this controller.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.allRoutes.onlyIf(myPersonalCheck);
|
|
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION onlyIfAuthenticated
|
|
<!-- js/server/modules/@arangodb/foxx/request_context.js -->
|
|
|
|
|
|
|
|
`Controller.allRoutes.onlyIfAuthenticated(code, description)`
|
|
|
|
This is equal to invoking `Route.onlyIfAuthenticated` on all routes bound to this controller.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.allRoutes.onlyIfAuthenticated(401, "You need to be authenticated");
|
|
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION pathParam
|
|
|
|
|
|
|
|
`Controller.allRoutes.pathParam(id, options)`
|
|
|
|
This is equal to invoking `Route.pathParam` on all routes bound to this controller.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.allRoutes.pathParam("id", joi.string().required().description("Id of the Foxx"));
|
|
|
|
app.get("/foxx/:id", function {
|
|
// Secured by pathParam
|
|
});
|
|
```
|
|
|
|
You can also pass in a configuration object instead:
|
|
|
|
```js
|
|
app.allRoutes.pathParam("id", {
|
|
type: joi.string(),
|
|
required: true,
|
|
description: "Id of the Foxx"
|
|
});
|
|
|
|
app.get("/foxx/:id", function {
|
|
// Secured by pathParam
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION bodyParam
|
|
|
|
|
|
|
|
`Controller.allRoutes.queryParam(id, options)`
|
|
|
|
This is equal to invoking `Route.queryParam` on all routes bound to this controller.
|
|
|
|
*Examples*
|
|
|
|
```js
|
|
app.allroutes.queryParam("id",
|
|
joi.string()
|
|
.required()
|
|
.description("Id of the Foxx")
|
|
.meta({allowMultiple: false})
|
|
});
|
|
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
});
|
|
```
|
|
|
|
You can also pass in a configuration object instead:
|
|
|
|
```js
|
|
app.allroutes.queryParam("id", {
|
|
type: joi.string().required().description("Id of the Foxx"),
|
|
allowMultiple: false
|
|
});
|
|
|
|
app.get("/foxx", function {
|
|
// Do something
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SECTION Before and After Hooks
|
|
|
|
You can use the following two functions to do something before or respectively
|
|
after the normal routing process is happening. You could use that for logging
|
|
or to manipulate the request or response (translate it to a certain format for
|
|
example).
|
|
|
|
!SUBSECTION before
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.before(path, callback)`
|
|
|
|
Defines an additional function on the route `path` which will be executed
|
|
before the callback defined for a specific HTTP verb is executed.
|
|
The `callback` function has the same signature as the `callback` in the
|
|
specific route.
|
|
You can also omit the `path`, in this case `callback` will be executed
|
|
before handleing any request in this Controller.
|
|
|
|
If `callback` returns the Boolean value `false`, the route handling
|
|
will not proceed. You can use this to intercept invalid or unauthorized
|
|
requests and prevent them from being passed to the matching routes.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.before('/high/way', function(req, res) {
|
|
//Do some crazy request logging
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION after
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.after(path, callback)`
|
|
|
|
Similar to `Controller.before(path, callback)` but `callback` will be invoked
|
|
after the request is handled in the specific route.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.after('/high/way', function(req, res) {
|
|
//Do some crazy response logging
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION around
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.around(path, callback)`
|
|
|
|
Similar to `Controller.before(path, callback)` `callback` will be invoked
|
|
instead of the specific handler.
|
|
`callback` takes two additional paramaters `opts` and `next` where
|
|
`opts` contains options assigned to the route and `next` is a function.
|
|
Whenever you call `next` in `callback` the specific handler is invoked,
|
|
if you do not call `next` the specific handler will not be invoked at all.
|
|
So using around you can execute code before and after a specific handler
|
|
and even call the handler only under certain circumstances.
|
|
If you omit `path` `callback` will be called on every request.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.around('/high/way', function(req, res, opts, next) {
|
|
//Do some crazy request logging
|
|
next();
|
|
//Do some more crazy request logging
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SECTION The Request and Response Objects
|
|
|
|
When you have created your FoxxController you can now define routes on it.
|
|
You provide each with a function that will handle the request. It gets two
|
|
arguments (four, to be honest. But the other two are not relevant for now):
|
|
|
|
* The **request** object
|
|
* The **response** object
|
|
|
|
These objects are provided by the underlying ArangoDB actions and enhanced
|
|
by the **BaseMiddleware** provided by Foxx.
|
|
|
|
!SUBSECTION The Request Object
|
|
|
|
The **request** object inherits several attributes from the underlying Actions:
|
|
|
|
* **compatibility**: an integer specifying the compatibility version sent by the
|
|
client (in request header **x-arango-version**). If the client does not send this
|
|
header, ArangoDB will set this to the minimum compatible version number. The
|
|
value is 10000 * major + 100 * minor (e.g. *10400* for ArangoDB version 1.4).
|
|
|
|
* *user*: the name of the current ArangoDB user. This will be populated only
|
|
if authentication is turned on, and will be *null* otherwise.
|
|
|
|
* *database*: the name of the current database (e.g. *_system*)
|
|
|
|
* *protocol*: *http* or *https*
|
|
|
|
* *server*: a JSON object with sub-attributes *address* (containing server
|
|
host name or IP address) and *port* (server port).
|
|
|
|
* *path*: request URI path, with potential [database name](../../Glossary/README.md#database-name) stripped off.
|
|
|
|
* *url*: request URI path + query string, with potential database name
|
|
stripped off
|
|
|
|
* *headers*: a JSON object with the request headers as key/value pairs.
|
|
**Note:** All header field names are lower-cased
|
|
|
|
* *cookies*: a JSON object with the request cookies as key/value pairs
|
|
|
|
* *requestType*: the request method (e.g. "GET", "POST", "PUT", ...)
|
|
|
|
* *requestBody*: the complete body of the request as a string
|
|
|
|
* *parameters*: a JSON object with all parameters set in the URL as key/value
|
|
pairs
|
|
|
|
* *urlParameters*: a JSON object with all named parameters defined for the
|
|
route as key/value pairs.
|
|
|
|
In addition to these attributes, a Foxx request objects provides the following
|
|
convenience methods:
|
|
|
|
!SUBSECTION body
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.body()`
|
|
|
|
Get the JSON parsed body of the request. If you need the raw version, please
|
|
refer to the *rawBody* function.
|
|
|
|
|
|
!SUBSECTION rawBody
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.rawBody()`
|
|
|
|
The raw request body, not parsed. The body is returned as a UTF-8 string.
|
|
Note that this can only be used sensibly if the request body contains
|
|
valid UTF-8. If the request body is known to contain non-UTF-8 data, the
|
|
request body can be accessed by using `request.rawBodyBuffer`.
|
|
|
|
|
|
!SUBSECTION rawBodyBuffer
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.rawBodyBuffer()`
|
|
|
|
The raw request body, returned as a Buffer object.
|
|
|
|
|
|
!SUBSECTION params
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.params(key)`
|
|
|
|
Get the parameters of the request. This process is two-fold:
|
|
|
|
* If you have defined an URL like */test/:id* and the user requested
|
|
*/test/1*, the call *params("id")* will return *1*.
|
|
* If you have defined an URL like */test* and the user gives a query
|
|
component, the query parameters will also be returned. So for example if
|
|
the user requested */test?a=2*, the call *params("a")* will return *2*.
|
|
|
|
|
|
!SUBSECTION cookie
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.cookie(name, cfg)`
|
|
|
|
Read a cookie from the request. Optionally the cookie's signature can be verified.
|
|
|
|
*Parameter*
|
|
|
|
* *name*: the name of the cookie to read from the request.
|
|
* *cfg* (optional): an object with any of the following properties:
|
|
* *signed* (optional): an object with any of the following properties:
|
|
* *secret*: a secret string that was used to sign the cookie.
|
|
* *algorithm*: hashing algorithm that was used to sign the cookie. Default: *"sha256"*.
|
|
|
|
If *signed* is a string, it will be used as the *secret* instead.
|
|
|
|
If a *secret* is provided, a second cookie with the name *name + ".sig"* will
|
|
be read and its value will be verified as the cookie value's signature.
|
|
|
|
If the cookie is not set or its signature is invalid, "undefined" will be returned instead.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```
|
|
var sid = request.cookie("sid", {signed: "keyboardcat"});
|
|
```
|
|
|
|
|
|
!SUBSECTION requestParts
|
|
Only useful for multi-part requests.
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`request.requestParts()`
|
|
|
|
Returns an array containing the individual parts of a multi-part request.
|
|
Each part contains a `headers` attribute with all headers of the part,
|
|
and a `data` attribute with the content of the part in a Buffer object.
|
|
If the request is not a multi-part request, this function will throw an
|
|
error.
|
|
|
|
|
|
!SECTION The Response Object
|
|
|
|
Every response object has the body attribute from the underlying Actions
|
|
to set the raw body by hand.
|
|
|
|
You provide your response body as a string here.
|
|
|
|
!SUBSECTION Response status
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.status(code)`
|
|
|
|
Set the status *code* of your response, for example:
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```
|
|
response.status(404);
|
|
```
|
|
|
|
|
|
!SUBSECTION Response set
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.set(key, value)`
|
|
|
|
Set a header attribute, for example:
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
response.set("Content-Length", 123);
|
|
response.set("Content-Type", "text/plain");
|
|
```
|
|
|
|
or alternatively:
|
|
|
|
```js
|
|
response.set({
|
|
"Content-Length": "123",
|
|
"Content-Type": "text/plain"
|
|
});
|
|
```
|
|
|
|
|
|
!SUBSECTION Response json
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.json(object)`
|
|
|
|
Set the content type to JSON and the body to the JSON encoded *object*
|
|
you provided.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
response.json({'born': 'December 12, 1915'});
|
|
```
|
|
|
|
|
|
!SUBSECTION Response cookie
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.cookie(name, value, cfg)`
|
|
|
|
Add a cookie to the response. Optionally the cookie can be signed.
|
|
|
|
*Parameter*
|
|
|
|
* *name*: the name of the cookie to add to the response.
|
|
* *value*: the value of the cookie to add to the response.
|
|
* *cfg* (optional): an object with any of the following properties:
|
|
* *ttl* (optional): the number of seconds until this cookie expires.
|
|
* *path* (optional): the cookie path.
|
|
* *domain* (optional): the cookie domain.
|
|
* *secure* (optional): mark the cookie as safe transport (HTTPS) only.
|
|
* *httpOnly* (optional): mark the cookie as HTTP(S) only.
|
|
* *signed* (optional): an object with any of the following properties:
|
|
* *secret*: a secret string to sign the cookie with.
|
|
* *algorithm*: hashing algorithm to sign the cookie with. Default: *"sha256"*.
|
|
|
|
If *signed* is a string, it will be used as the *secret* instead.
|
|
|
|
If a *secret* is provided, a second cookie with the name *name + ".sig"* will
|
|
be added to the response, containing the cookie's HMAC signature.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```
|
|
response.cookie("sid", "abcdef", {signed: "keyboardcat"});
|
|
```
|
|
|
|
|
|
!SUBSECTION Response send
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.send(value)`
|
|
|
|
Sets the response body to the specified *value*. If *value* is a Buffer
|
|
object, the content type will be set to `application/octet-stream` if not
|
|
yet set. If *value* is a string, the content type will be set to `text/html`
|
|
if not yet set. If *value* is an object, it will be treated as in `res.json`.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
response.send({"born": "December 12, 1915"});
|
|
response.send(new Buffer("some binary data"));
|
|
response.send("<html><head><title>Hello World</title></head><body></body></html>");
|
|
```
|
|
|
|
|
|
!SUBSECTION Response sendFile
|
|
<!-- js/server/modules/@arangodb/foxx/base_middleware.js -->
|
|
|
|
|
|
|
|
`response.sendFile(filename, options)`
|
|
|
|
Sets the content of the specified file as the response body. The filename
|
|
must be absolute. If no content type is yet set for the response, the
|
|
response's content type will be determined automatically based
|
|
on the filename extension. If no content type is known for the extension,
|
|
the content type will default to `application/octet-stream`.
|
|
|
|
The `options` array can be used to control the behavior of sendFile.
|
|
Currently only the following option exists:
|
|
- `lastModified`: if set to true, the last modification date and time
|
|
of the file will be returned in the `Last-Modified` HTTP header
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
response.sendFile('/tmp/results.json');
|
|
response.sendFile(applicationContext.fileName('image.png'), { lastModified: true });
|
|
```
|
|
|
|
|
|
!SECTION Controlling Access to Foxx Applications
|
|
|
|
Access to Foxx applications is controlled by the regular authentication mechanisms
|
|
present in ArangoDB. The server can be run with or without HTTP authentication.
|
|
|
|
If authentication is turned on,
|
|
then every access to the server is authenticated via HTTP authentication. This
|
|
includes Foxx applications. The global authentication can be toggled
|
|
via the configuration option.
|
|
|
|
If global HTTP authentication is turned on, requests to Foxx applications will
|
|
require HTTP authentication too, and only valid users present in the *_users*
|
|
system collection are allowed to use the applications.
|
|
|
|
Since ArangoDB 1.4, there is an extra option to restrict the authentication to
|
|
just system API calls, such as */_api/...* and */_admin/...*. This option can be
|
|
turned on using the
|
|
"server.authenticate-system-only" configuration option. If it is turned on,
|
|
then only system API requests need authentication whereas all requests to Foxx
|
|
applications and routes will not require authentication.
|
|
|
|
This is recommended if you want to disable HTTP authentication for Foxx applications
|
|
but still want the general database APIs to be protected with HTTP authentication.
|
|
|
|
If you need more fine grained control over the access to your Foxx application,
|
|
we built an authentication system you can use. Currently we only support cookie-based
|
|
authentication, but we will add the possibility to use Auth Tokens and external OAuth
|
|
providers in the near future. Of course you can roll your own authentication mechanism
|
|
if you want to, and you can do it in an application-specific way if required.
|
|
|
|
To use the per-application authentication, you should first turn off the global
|
|
HTTP authentication (or at least restrict it to system API calls as mentioned above).
|
|
Otherwise clients will need HTTP authentication and need additional authentication by
|
|
your Foxx application.
|
|
|
|
To have global HTTP authentication turned on for system APIs but turned off for Foxx,
|
|
your server startup parameters should look like this:
|
|
|
|
--server.disable-authentication false --server.authenticate-system-only true
|
|
|
|
**Note**: During development, you may even turn off HTTP authentication completely:
|
|
|
|
--server.disable-authentication true --server.authenticate-system-only true
|
|
|
|
Please keep in mind that turning HTTP authentication off completely will allow
|
|
unauthenticated access by anyone to all API functions, so do not use this is production.
|
|
|
|
Now it's time to configure the application-specific authentication. We built a small
|
|
[demo application](https://github.com/arangodb/foxx-authentication) to demonstrate how
|
|
this works.
|
|
|
|
To use the application-specific authentication in your own app, first activate it in your controller.
|
|
|
|
!SUBSECTION Active Authentication
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`Controller.activateAuthentication(opts)`
|
|
|
|
To activate authentication for this controller, call this function before defining any routes.
|
|
In the `opts` object you can set the following keys:
|
|
|
|
* *type*: Currently we only support *cookie*, but this will change in the future
|
|
* *cookieLifetime*: An integer. Lifetime of cookies in seconds
|
|
* *cookieName*: A string used as the name of the cookie
|
|
* *sessionLifetime*: An integer. Lifetime of sessions in seconds
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.activateAuthentication({
|
|
type: "cookie",
|
|
cookieLifetime: 360000,
|
|
cookieName: "my_cookie",
|
|
sessionLifetime: 400,
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION Login
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`FoxxController#login(path, opts)`
|
|
|
|
Add a route for the login. You can provide further customizations via the
|
|
the options:
|
|
|
|
* *usernameField* and *passwordField* can be used to adjust the expected attributes
|
|
in the *post* request. They default to *username* and *password*.
|
|
* *onSuccess* is a function that you can define to do something if the login was
|
|
successful. This includes sending a response to the user. This defaults to a
|
|
function that returns a JSON with *user* set to the identifier of the user and
|
|
* *key* set to the session key.
|
|
* *onError* is a function that you can define to do something if the login did not
|
|
work. This includes sending a response to the user. This defaults to a function
|
|
that sets the response to 401 and returns a JSON with *error* set to
|
|
"Username or Password was wrong".
|
|
|
|
Both *onSuccess* and *onError* should take request and result as arguments.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.login('/login', {
|
|
onSuccess(req, res) {
|
|
res.json({"success": true});
|
|
}
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION Logout
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`FoxxController#logout(path, opts)`
|
|
|
|
This works pretty similar to the logout function and adds a path to your
|
|
app for the logout functionality. You can customize it with a custom *onSuccess*
|
|
and *onError* function:
|
|
|
|
* *onSuccess* is a function that you can define to do something if the logout was
|
|
successful. This includes sending a response to the user. This defaults to a
|
|
function that returns a JSON with *message* set to "logged out".
|
|
* *onError* is a function that you can define to do something if the logout did not
|
|
work. This includes sending a response to the user. This defaults to a function
|
|
that sets the response to 401 and returns a JSON with *error* set to
|
|
"No session was found".
|
|
|
|
Both *onSuccess* and *onError* should take request and result as arguments.
|
|
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.logout('/logout', {
|
|
onSuccess(req, res) {
|
|
res.json({"message": "Bye, Bye"});
|
|
}
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION Register
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
`FoxxController#register(path, opts)`
|
|
|
|
This works pretty similar to the logout function and adds a path to your
|
|
app for the register functionality. You can customize it with a custom *onSuccess*
|
|
and *onError* function:
|
|
|
|
* *onSuccess* is a function that you can define to do something if the registration was
|
|
successful. This includes sending a response to the user. This defaults to a
|
|
function that returns a JSON with *user* set to the created user document.
|
|
* *onError* is a function that you can define to do something if the registration did not
|
|
work. This includes sending a response to the user. This defaults to a function
|
|
that sets the response to 401 and returns a JSON with *error* set to
|
|
"Registration failed".
|
|
|
|
Both *onSuccess* and *onError* should take request and result as arguments.
|
|
|
|
You can also set the fields containing the username and password via *usernameField*
|
|
(defaults to *username*) and *passwordField* (defaults to *password*).
|
|
If you want to accept additional attributes for the user document, use the option
|
|
*acceptedAttributes* and set it to an array containing strings with the names of
|
|
the additional attributes you want to accept. All other attributes in the request
|
|
will be ignored.
|
|
|
|
If you want default attributes for the accepted attributes or set additional fields
|
|
(for example *admin*) use the option *defaultAttributes* which should be a hash
|
|
mapping attribute names to default values.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.register('/logout', {
|
|
acceptedAttributes: ['name'],
|
|
defaultAttributes: {
|
|
admin: false
|
|
}
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSECTION Change Password
|
|
<!-- js/server/modules/@arangodb/foxx/controller.js -->
|
|
|
|
|
|
|
|
FoxxController#changePassword(route, opts)`
|
|
|
|
Add a route for the logged in user to change the password.
|
|
You can provide further customizations via the
|
|
the options:
|
|
|
|
* *passwordField* can be used to adjust the expected attribute
|
|
in the *post* request. It defaults to *password*.
|
|
* *onSuccess* is a function that you can define to do something if the change was
|
|
successful. This includes sending a response to the user. This defaults to a
|
|
function that returns a JSON with *notice* set to "Changed password!".
|
|
* *onError* is a function that you can define to do something if the login did not
|
|
work. This includes sending a response to the user. This defaults to a function
|
|
that sets the response to 401 and returns a JSON with *error* set to
|
|
"No session was found".
|
|
|
|
Both *onSuccess* and *onError* should take request and result as arguments.
|
|
|
|
|
|
**Examples**
|
|
|
|
|
|
```js
|
|
app.changePassword('/changePassword', {
|
|
onSuccess(req, res) {
|
|
res.json({"success": true});
|
|
}
|
|
});
|
|
```
|
|
|
|
|
|
|
|
!SUBSUBSECTION Restricting routes
|
|
|
|
To restrict routes, see the documentation for Documenting and Restraining the routes.
|