Angel
1.x
1.x
  • Introduction
  • Example Projects
  • Awesome Angel
  • 1.1.0 Migration Guide
  • Social
    • Angel on Gitter
    • Angel on Medium
    • Angel on YouTube
  • The Basics
    • Installation & Setup
      • Without the Boilerplate
    • Requests & Responses
    • Dependency Injection
    • Basic Routing
    • Request Lifecycle
    • Middleware
    • Controllers
    • Handling File Uploads
    • Using Plug-ins
    • Rendering Views
    • REST Client
    • Testing
    • Error Handling
    • Pattern Matching and Parameter
    • Command Line
  • Flutter
    • Writing a Chat App
    • Flutter helper widgets
  • Services
    • Service Basics
    • TypedService
    • In-Memory
    • Custom Services
    • Hooks
      • Bundled Hooks
    • Database-Agnostic Relations
    • Database Adapters
      • MongoDB
      • RethinkDB
      • JSON File-based
  • Plug-ins
    • Authentication
    • Configuration
    • Diagnostics & Logging
    • Reverse Proxy
    • Service Seeder
    • Static Files
    • Validation
    • Websockets
    • Server-sent Events
    • Toggle-able Services
  • Middleware/Finalizers
    • CORS
    • Response Compression
    • Security
    • File Upload Security
    • shelf Integration
    • User Agents
    • Pagination
    • Range, If-Range, Accept-Ranges support
  • PostgreSQL ORM
    • Model Serialization
    • Query Builder + ORM
    • Migrations
  • Deployment
    • Running in Isolates
    • Configuring SSL
    • HTTP/2 Support
    • Ubuntu and nginx
    • AppEngine
    • Production Mode
  • Front-end
    • Mustache Templates
    • Jael template engine
      • Github
      • Basics
      • Custom Elements
      • Strict Resolution
      • Directive: declare
      • Directive: for-each
      • Directive: extend
      • Directive: if
      • Directive: include
      • Directive: switch
    • compiled_mustache-based engine
    • html_builder-based engine
    • Markdown template engine
    • Using Angel with Angular
  • Advanced
    • API Documentation
    • Contribute to Angel
    • Scaling & Load Balancing
    • Standalone Router
    • Writing a Plugin
    • Task Engine
    • Hot Reloading
    • Real-time polling
Powered by GitBook
On this page
  • Middleware
  • Denying Requests via Middleware
  • Declaring Middleware
  • Named Middleware
  • Global Middleware
  • waterfall
  • Maintaining Code Readability
  • Next Up...
  1. The Basics

Middleware

PreviousRequest LifecycleNextControllers

Last updated 6 years ago

Middleware

Sometimes, it becomes to recycle code to run on multiple routes. Angel allows for this in the form of middleware. Middleware are frequently used as authorization filters, or to serialize database data for use in subsequent routes. Middleware in Angel can be any route handler, whether a function or arbitrary data. You can also throw exceptions in middleware.

Denying Requests via Middleware

A middleware should return either true or false. If false is returned, no further routes will be executed. If true is returned, route evaluation will continue. (more on request handler return values ).

As you can imagine, this is perfect for authorization filters.

Declaring Middleware

You can call a router's chain method (recommended!), or assign middleware in the middleware parameter of a route method.

// Both ways ultimately accomplish the same thing

app
  .chain((req, res) {
    res.write("Hello, ");
    return true;
  }).get('/', 'world!');

app.get('/', 'world!', middleware: [someListOfMiddleware]);

Named Middleware

Router instances allow you to assign names to middleware via registerMiddleware. After registering a middleware, you can include it in a route just by passing its name into the middleware array. If you are mount-ing another Routable or Angel instance, you can map all its middleware into a namespace by passing it to the use call.

app.registerMiddleware('deny', (req, res) async => false);
app.get('/no', 'This will never show', middleware: ['deny']);

// Using annotation
@Middleware(const ['deny'])
Future never(RequestContext req, ResponseContext res) async {
  return "This will never show either";
}

app.get('/yes', never);

// Using a middleware namespace
Angel parent = new Angel();
parent.use('/child', app, middlewareNamespace: 'child');
parent.get('/foo', 'Never shown', middleware: ['child.deny']);

Global Middleware

app.use((req, res) async => res.end());

For more complicated middleware, you can also create a class:

class MyMiddleware {
  Future<bool> call(Angel app) async {
    // Do something...
  }
}

Canonically, when using a class as a request handler, it should provide a handleRequest(RequestContext, ResponseContext) method. This pattern is seen throughout many Angel plugins, such as VirtualDirectory or Proxy.

class MyCanonicalHandler {
 Future<bool> handleRequest(RequestContext req, ResponseContext res) async {
  // Do something cool...
 }
}

app.use(new MyCanonicalHandler().handleRequest);

waterfall

app.chain(waterfall([
  banIp('127.0.0.1'),
  'auth',
  ensureUserHasAccess(),
  (req, res) async => true,
  takeOutTheTrash()
])).get(...);

Maintaining Code Readability

Note that a cleaner representation is:

app.get('/the-route', waterfall([
  banIp('127.0.0.1'),
  'auth',
  ensureUserHasAccess(),
  (req, res) async => true,
  takeOutTheTrash()
  (req, res) {
   // Your route handler here...
  }
]));

In general, consider it a code smell to stack multiple handlers onto a route like this; it hampers readability, and in general just doesn't look good.

Instead, when you have multiple handlers, you can split them into multiple waterfall calls, assigned to variables, which have the added benefit of communicating what each set of middleware does:

var authorizationMiddleware = waterfall([
 banIp('127.0.0.1'),
 requireAuthentication(),
 ensureUserHasAccess(),
]);

var someOtherMiddleware = waterfall([
 (req, res) async => true,
 takeOutTheTrash(),
]);

var theActualRouteHandler = (req, res) async {
 // Handle the request...
};

app.get('/the-route', waterfall([
 authorizationMiddleware,
 someOtherMiddleware,
 theActualRouteHandler,
]);

Tip: Prefer using named functions as handlers, rather than anonymous functions, or concrete objects.

Next Up...

To add a handler that handles every request, call app.use. This is equivalent to calling app.all('*', <handler>). (more info on request lifecycle ).

The reason for this is that a name like handleRequest makes it very clear to anyone reading the code what it is supposed to do. This is the same rationale behind providing a configureServer method.

You can chain middleware (or any request handler together), if you do not feel like making multiple chain calls, or if it is impossible to call chain multiple times, using the function:

Take a good look at in Angel!

here
controllers
waterfall
controllers
Middleware
Denying Requests via Middleware
Declaring Middleware
Named Middleware
Global Middleware
waterfall([...])
**Maintaining Code Readability
Next Up...
here