Now that we have slightly improved our greet handler, let's try and refactor our user handler.
// old user handler
const userHandler = fileHandler()
The user handler uses the filesystem as a simple mockup of HTTP API for user profiles. Everything works fine except that our flat-file database is made of files with .json
extension. What if we want to serve the HTTP API as /user/john
instead of /user/john.json
? Do we have to rename the physical files just for it?
Quiver provides a simple way to extend components by composing them using the Middleware and Filter constructs. In this tutorial we are going to create two simple middleware components to alter the behavior of the file handler.
When the file handler accepts a request, it knows which file path to serve by checking args.path
. This letiable is currently set by the main router component based on the special :restpath
route parameter.
Unlike normal route parameters, :restpath
allow nested paths so the file handler can technically serve paths such as /user/subdir/other.html
. But because our flat files are all in one directory, we can simplify the path resolution by telling file handler which file path to serve ourselves.
The Args Filter component is a type of middleware component that modifies the args
before the actual handler receives it.
const userHandler = fileHandler()
.argsFilter(args => {
args.path = '/' + args.username + '.json'
})
We can apply an args filter to the file handler by calling the chainable .argsFilter()
method. Inside argsFilter()
, we define a function that takes in args
and compute the filepath args.path
based on args.username
. This way without modifying the file handler code, we can make it serve files based on args.username
instead.
const main = router()
.paramRoute('/user/:username', userHandler)
So now with the new interface, the user route can be modified to set the user parameter to args.username
.
The .argsFilter()
method earlier is a syntactic sugar for creating an args filter component and applying it to the handler component. It is also possible to define the filter as separate component and then apply it later on.
import { argsFilter } from 'quiver/component'
const userPathFilter = argsFilter(
args => {
args.path = '/' + args.username + '.json'
})
const userHandler = fileHandler()
.middleware(userPathFilter)
The code above shows a more verbose way to define the args filter using the argsFilter()
constructor from quiver-component
. The result is a userPathFilter
middleware component that we can later on apply to the user file handler. Once we define a middleware component, we can apply it to a handler component using the chainable .middleware()
method.
Now that we customize how the user handler process args
, let's also modify how the file handler is configured. Recall that file handler serve static files from the base directory specified in config.dirPath
. If we set this in the global config and have multiple file handler instances, then all the file handlers would reference the same dirPath
and serve the same directory.
We can allow multiple file handlers to read from different config keys to make them serve different directories. This is done by using the config alias middleware that alias a config key to another key. Quiver components have a convenient method .configAlias()
to set up the alias easily.
const userHandler = fileHandler()
.configAlias({
dirPath: 'userDir'
})
With this, the middleware will set config.dirPath
to the same value as config.userDir
before the file handler is built. This way we can set userDir
in our config, which makes it clearer for users on what that configuration is for.
Similar to args filter, the .configAlias()
method is a syntactic sugar for creating a configAliasMiddleware
component and applying it to the handler component:
import { configAliasMiddleware } from 'quiver/component'
const userDirAlias = configAliasMiddleware({
dirPath: 'userDir'
})
const userHandler = fileHandler()
.middleware(userDirAlias)
The code above shows a more verbose way of defining config alias middleware.
Applying both middlewares together, we get our final user handler component defined as follow:
// user.js
const userHandler = fileHandler()
.argsFilter(args => {
args.path = '/' + args.username + '.json'
})
.configAlias({
dirPath: 'userDir'
})
Here we also separate out different components and put them in different source files:
- user.js - user handler component.
- greet.js - greet handler component moved with code unmodified.
- component.js - main router component and hello handler.
Our config.js now has userDir
set instead of dirPath
:
// config.js
export const config = {
greet: 'Yo',
userDir: 'static/user'
}
$ npm start 04
Now with everything set, if we run the server we should be able to query the user API without the .json
extension:
$ curl http://localhost:8080/user/mikeal
{
"username": "mikeal",
"name": "Mikeal Rogers",
"email": "[email protected]"
}