Service Basics

Services

One of the main concepts within Angel, which is borrowed from FeathersJS, is a service. You more than likely have already dealt with another implementation of the service concept. In Angel, a service is a class that acts as a Web interface and exposes CRUD actions operating on a set of data. Angel services extend Routable, and thus can be mounted on a certain path and become REST endpoints.
The Angel core library includes the Service base class, as well as two in-memory service classes. Database adapter packages, such as package:angel_mongo include service classes that let you interact with a database without writing complex code yourself.
Services can also be filtered or reacted to with service hooks.
A service looks like this:
1
class MyService extends Service<String, Map<String, dynamic>> {
2
// GET /
3
// Fetch all resources. Usually returns a List.
4
@override
5
Future<List<Map<String, dynamic>>> index([Map<String, dynamic> params]);
6
​
7
// GET /:id
8
// Fetch one resource, by its ID
9
@override
10
Future<Map<String, dynamic>> read(String id, [Map<String, dynamic> params]);
11
​
12
// POST /
13
// Create a resource. This endpoint should return
14
// the created resource.
15
@override
16
Future<Map<String, dynamic>> create(Map<String, dynamic> data, [Map<String, dynamic> params]);
17
​
18
// PATCH /:id
19
// Modifies a resource. Clients can submit only the data
20
// they want to change, and the corresponding resource will
21
// have only those fields changed. This endpoint should return
22
// the modified resource.
23
@override
24
Future<Map<String, dynamic>> modify(String id, Map<String, dynamic> data, [Map<String, dynamic> params]);
25
​
26
// POST /:id
27
// Overwrites a resource. The existing resource is completely
28
// replaced by the new data. This endpoint should return the
29
// new resource.
30
@override
31
Future<Map<String, dynamic>> update(String id, Map<String, dynamic> data, [Map<String, dynamic> params]);
32
​
33
// DELETE /:id
34
// Deletes a resource. This endpoint should return the
35
// deleted resource.
36
@override
37
Future<Map<String, dynamic>> remove(String id, [Map<String, dynamic> params]);
38
}
Copied!
There are meta-methods that default to delegating to the above:
    findOne
    readMany
You can override these for your service, if it will improve performance.

Service Parameters and Middleware

You might notice that each service method accepts an optional Map of parameters. When accessed via HTTP (i.e., not over Websockets), req.query or req.bodyAsMap is passed here (query for index, read and delete, bodyAsMap for create, update and modify). To pass custom parameters to a service, you should create a middleware to do so. @Middleware annotations can be prepended to service classes or service methods. For example, the following will pass foo='bar' to every method in the service:
1
Future<bool> myMiddleware(RequestContext req, res) async {
2
req.queryParameters['foo'] = 'bar';
3
return true;
4
}
5
​
6
@Middleware(const [myMiddleware])
7
class MyService extends Service {
8
// Responds with "['bar']"
9
@override index([Map params]) async => [params['query']['foo']];
10
}
Copied!
Additionally, when accessed by a client, params will contain a field called provider.
1
class MyService extends Service {
2
@override
3
create(data, [Map params]) async {
4
if (params == null || params['provider'] == null) {
5
// Accessed via server
6
}
7
}
8
}
Copied!
provider will be a Providers class, whose String via will tell you where the service is being accessed from, i.e. 'rest', 'graphql' or 'websocket'.

Mounting Services

As mentioned above, services extend Routable, so you can simply app.use() them. You can also supplement them with additional routes or middleware, placed before the mounting of a service:
1
app.get("/user/:id/todos", ioc((id) => fetchUserTodos(id))));
2
​
3
// Another way to apply a middleware to a service
4
app.all("/user/*", [someMiddleware], middleware: ['some', 'more', 'middleware']);
5
​
6
app.use('/user', TypedService<User>(MongoService(db.collection("users"))));
7
​
8
// Access app services. Returns a HookedService if there is one, otherwise just the plain service.
9
// Leading and trailing slashes are ignored.
10
var service = app.findService('user'); // The user service
11
var service = app.service<String, Map<String, dynamic>>('secret');
Copied!

Additional Notes

Important things to consider when writing your own service:
    ​mongo is a good reference implementation]
    Services need only worry about handling Maps. Object serialization should be handled by angel_serialize, another serializer, or TypedService.
    Allowing users to query the service via query string is optional (see allowQuery)
    Allowing users to remove all entries is optional, and should be disabled by default
      DELETE /null should trigger an evaluation of allowRemoveAll
      Service.toId will return null in these cases
    Always return the most recent representation of the data
      After remove, return the old item
      After modify/update, return what the item looks like in the database
    modify and update are not interchangeable!
      modify merges changes into an existing item
      update overwrites an existing item
      BOTH should create an item with the given ID if it does not already exist
Last modified 4mo ago