Treehouse
Treehouse is a lightweight Koa, event-based server that provides common configuration and setup in an Object-Oriented design for both REST and GraphQL servers. The purpose of Treehouse is to help speed up the development of Nodejs services by abstracting out the boilerplate and basic configuration and allow you to focus on your custom middleware or handlers. In addition to providing a core Koa server, Treehouse also exposes other common utilities:
- Bunyan
Logger
- Application-specific Exceptions
- GraphQL Server provided by Apollo Server 2.0
Additional documentation and API: here.
Why Treehouse
We get it...building your own server from scratch is fun. So if you like taking various libraries and stitching them together, then this library is probably not for you. However, maybe you like the way this library is setup and is exactly the way you'd do it. Or maybe you're tired of seeing "boilerplates" that set everything up for you, tell you how to code your server, etc. etc., but still don't want to set all the middleware and writing common utilities. If you find yourself saying "that's me!", then this library is for you. Treehouse aims to find the correct abstraction and empower you to quickly standup a server so you can focus on the application-specific code.
NOTE
This project is currently under development and is subject to change. Please refer to the TODOS Section for the work remaining before version 1.0 is tagged and this enters a table release cycle.
Installing
npm i @harmonizly/treehouse
You can find the deployment versions here.
Lifecycle
The lifecycle of the server is:
- Instantiation
- Initialized
- Start HTTP(S)
- Running
- Stop
- Destroying
- Destroyed
Every request will run through the following middleware before being evaluated by the router (koa-router):
The middleware transaction
is used to tag each request with a unique "transaction" ID using NodeJS domain's to track a request through it's entire time spent in the app.
The access logger
middleware uses the bunyan
logger to record details about each request prior to entering any custom middleware or handlers. Please refer to the Logging Section for more details.
The error
middleware wraps all custom middleware and handlers in a try/catch
to provide a uniform way to log the error and send the appropriate response. The error
middleware ensures that the caught Error
is some form of ApiError
, so it is recommended that, if you throw a custom Error
from any middleware or handlers, you extend ApiError
.
Third-party middleware references:
Note! When adding your app-specific handlers
and middleware
, please take some time to understand koa-router
and the difference between global middleware and route-specific middleware.
Details
The GraphqQLServer
is an added layer on top of the Treehouse Server
and sets up apollo-server 2.0
from the provided schema object using applyMiddleware
. This merely attaches an additional router to the Koa
app (from the Server
) to handle graphql
requests.
A couple notes of interest:
- The server instance listens to
process:exit
, which callsdestroy
to handle process termination. - If
process.send
exists (i.e. when the process is managed bypm2
), the instance sendsready
acrossprocess.send
to notify anyone waiting that the server has beed started. - When the server is destroying, the instance will
emit
destroy
onprocess
to notify anyone watching that the server is going down. - The
transactionId
is included with every logging output. Thetransaction
context that's included in every logger statement includes information about the user and session, if that data exists. This assumes that an authorized user is attached to the request context asuser
and the session is attached to the request context assession
.
Usage
Treehouse requires only a configuration object, but provides flexibility in how you build you app. The most basic, quick way to get your server running with Treehouse with or without custom middleware and one or more application-specific routers is:
import Server from 'treehouse';
const server = new Server(config)
.use(app => {
// Attach any middleware here
// OR
// Attach any application-specific routers to the app
// Remember! Order matters when using a connect-based server
})
// Provide a callback function to have custom code executed when the server starts
.onStart(...)
// Provide a callback function to have custom code executed when the server stops
.onStop(...);
server.start();
The GraphQL Server follows the same pattern, but requires a schema
object:
import { GraphqQLServer } from 'treehouse';
import { makeExecutableSchema } from 'apollo-server-koa';
const schema = makeExecutableSchema(typedefs, resolvers);
const server = new GraphqQLServer(config, schema)
.use(app => {
// Attach any middleware here
// OR
// Attach any application-specific routers to the app
// Remember! Order matters when using a connect-based server
})
// Provide a callback function to have custom code executed when the server starts
.onStart(...)
// Provide a callback function to have custom code executed when the server stops
.onStop(...);
server.start();
Treehouse can also be extended to provide more complex implementations:
import Server from 'treehouse';
class MyServer extends Server {
constructor() {
super(config);
}
initialize() {
// Override initialize to either override the default middleware or to have
// more flexibility in attaching middleware you want to use.
// If you wish to keep the default middleware in addition to the ones you
// add, don't forget to call `super.initialize();`!
}
start() {
// Override start to change the startup behavior.
// Use `super.start();` if you want to augment start before or after the
// server starts up
}
stop() {
// Override stop to change the startup behavior.
// Use `super.stop();` if you want to augment stop before or after the
// server stops accepting connections
}
}
The GraphQL Server
can also be overridden this way. The only difference is that the constructor must be provided the schema
object.
Logging
Configuration
It is highly recommended that you use node-config as your configuration library and to pass the resulting configuration object to Treehouse. The following an example configuration showing all the available options:
{
bodyparser: { ... }, // configuration for koa-bodyparser
cors: { ... }, // configuration for koa-cors
compress: {}, // configuration for koa-compress
// The following configuration option is only needed if you're using the GraphQL Server
graphql: {
gui: process.env.NODE_ENV === 'development',
introspection: true,
url: "/graphql",
},
// Treehouse logger configuration
// Optional
loggers: {
handlers: {
access: {
name: 'access',
level: 'info',
},
},
streams: {
// Under construction
},
},
server: {
hostname: '0.0.0.0',
port: 3000,
secure: false,
ssl: {
key: '<file path>',
cert: '<file path>'
},
},
}
Building
We use Make
to run various build tasks. See Makefile
for details on the build process.
To build a deployment (production) dist
, run make
.
To build development dist
, run make dev
.
Contributing
Ensure that you are pushing to a branch off of master
and not to master
directly.
Please follow standard git flow practices for merging branches with a PR.
The dist
bundle is created using webpack (located under the build/
directory). To build in the production
mode, ensure that the NODE_ENV
environment variable is set to production
.
Distribution is currently hosted through Github, so the binary is currently tracked.
To build and "deploy", run make
at the root project directory and commit the resulting dist
bundle.
TODOs
- [ ] Complete tests (tested manually, but need full suite)
- [ ] Finish setting up travis for complete testing, build, and deploy
- [ ] Support custom streams for bunyan in log configuration
- [x] Apollo Server V2
- [ ] Migrate to Typescript
- [x] Improve Server interface around stop, start, middleware, & routers
- [ ] Update documentation with updates interfaces
Testing
Under construction.
All testing is done using jest.
To run tests, use npm test
. You can provide additional arguments to Mocha
by running npm test -- <opts>
.