#Kwyjibo A set of TypeScript Decorators and helpers for a better node.js+Express experience.
##TL;DR Watch this video
##Key features
- Requirements
- Express integration
- Controllers and Actions
- Custom mount conditions
- Error Handling
- Tests execution and automation
- Documentation generator
##Quickstart
-
Install Visual Studio Code
-
Install
yo
with thegenerator-kwyjibo
package, and the required dependencies for every-day development (typescript
)npm install --global yo generator-kwyjibo typescript@beta
-
Use the
generator-kwyjibo
to scaffold a new web app in the current directory. When asked, give a name to the app and answerYes
to every generator optionyo kwyjibo
-
Start Visual Studio Code
code .
-
Press
F1
and type>Run Build Task
to build the app -
Press
F5
to start the Kwyjibo app inhttp://localhost:3000
-
You did it! Your Kwyjibo app is up and running!
##Requirements
To use the Kwyjibo framework with a Node.js+Express web app, the minimum requirements are:
- Node.js 6.x
- Express 4.14.0
- TypeScript 2.0.0 (beta), with:
experimentalDecorators
support- ECMAScript 6 target
(in your tsconfig.json
file, inside compilerOptions
set experimentalDecorators
to true
and target
to es6
)
##Express integration
The easiest way to use Kwyjibo is using the Yeoman generator, as it is explained in the quickstart. However, if you already have an Express application, or just don't want to use the generator, you can use this steps to integrate Kwyjibo with an existing Express app.
Once you have an Express app up and running (and using TypeScript), go to a terminal and run npm install --save kwyjibo
to add it as a dependency to you app. Then, open the app entrypoint (let's say, app.ts
) and add the following line at the beginning:
import * as Kwyjibo from "kwyjibo"
And right after creating the Http server, add the following lines (assuming expressApp
is an object containing the Express app):
// Init all Kwyjibo controllers and tests (assuming "tests" and "controllers" folders)
// To use custom folders, pass the folder names as extra parameters to initialize
Kwyjibo.initialize(App.express);
This will configure the framework loggers, and load all the tests and controllers that are inside the tests
and controllers
folders
##Controllers and Actions
The main components in a Kwyjibo app are the controllers and their actions. Each controller is a mount point for a set of actions, and each action can handle a request to a specific path and HTTP method.
###Controllers
The controllers must be decorated with the @Controller
decorator, specifying the mount point:
@Controller("/myMountPoint")
will mount the controller in/myMountPoint
.@Controller(AnotherController, "/myMountPoint")
will mount the controller in the specified mount point, but usingAnotherController
as its root (for instance, this could be ended up being mounted as/someMountPoint/myMountPoint
.
You can - optionally - add the @DocController
decorator to add documentation and the @Middleware
decorator to apply middlewares to all the controller actions
###Actions
Each action is a method in the controller with either, a @Get
, @Post
or @Method
decorator to specify its route and HTTP method, or at least one of the @DocAction
or @ActionMiddleware
decorators, and it will use the default mount point (the method name, using the GET
HTTP method)
The @DocAction
the same way @DocController
does, but for actions, and the @ActionMiddleware
to apply middlewares to particular actions.
By default, the action methods receive at least a context: Kwyjibo.Context
parameter that allows it to access the request and response objects and can return:
- If the action just returns
200 OK
, invokes tocontext.response.render
or manually handles thecontext.response
: void
Promise<void>
- if the actions returns
200 OK
and sends a string as a response (for instance, an HTML): string
Promise<string>
- If the action returns a
200 OK
and sends ajson
object as a response: Object
Promise<Object>
In any of those cases, if an exception is thrown (or the promise is rejected), the exception will be handled by the error handler middlewares configured in Express.
Also, a method can return an HttpError for which the correct status and message will be sent. For example:
async someMethod(context:Context): Promise<Object|HttpError> {
let retObj = await someMethodThatBringsTheObject();
if(retObj == undefined) {
return NotFound("Cannot find the requested object");
}
return retObj;
}
###Parameters
To use request parameters from the body, route path, querystring, headers or cookies, you can decorate any action parameter but the first (that must be the context) with the following decorators:
@FromBody("paramName")
@FromPath("paramName")
@FromQuery("paramName")
@FromHeader("paramName")
@FromCookie("paramName")
For instance, to create a controller for users operations:
- Create a
controllers
folder in your app root and create ausersController.ts
file inside. - Add the following code:
import * as K from "kwyjibo";
@K.Controller("/users")
@K.DocController("Users Controller.")
@K.Middleware(UsersController.loggingMiddleware)
class UsersController {
static loggingMiddleware(req: Express.Request, res: Express.Response, next: Function) {
console.log("Request to: " + req.path);
}
@K.Get("/")
@K.DocAction(`Users index`)
index(context: K.Context): string {
return "<html><body><ul><li>/list: all users</li><li>/user/:id: specifi user</li></ul></body></html>";
}
@K.Get("/list")
@K.DocAction(`Return all users`)
allUsers(context: Context): Object {
let users = UsersRepository.getAllUsers();
return users; // this action will send a json object
}
@Get("/user/:id")
@DocAction(`Return a specific user`)
oneUser(context: Context, @FromPath("id") id: string): string {
let user = UsersRepository.getUser(id);
return user;
}
}
###Migration from standard Express
If you want to use the standard Express route method signature, instead of just receiving the context
object (useful to migrate classic Express apps to Kwyjibo), you can use the @ExpressCompatible
decorator and create methods like this:
@Get("/somewhere")
@ExpressCompatible()
myExpressCompatibleAction(req: Express.Request, res: Express.Response, next: Function): void {
// do something
}
##Custom mount conditions
If you want to mount controllers conditionally, you can use the @MountCondition
decorator.
###Dev environment
When the node app is started with the environment variable NODE_ENV = development
, every controller that doesn't have it's root endpoint mapped to an action will autogenerate an index with links to every action available at that endpoint.
Also, if you have controllers that should only be exposed in development environment, you can use the @Dev
controller decorator (a special case of a custom mount condition) and it will only be mounted if that condition is met.
##Error Handling
Kwijibo will automatically handle errors thrown inside actions and send a 500 Internal Server Error
response.
However, you can throw known error types that Kwyjibo can handle:
HttpError
: custom HttpError, with message and status codeInternalServerError
: 500 error with messageNotFound
: 404 error with messageBadRequest
: 400 error with messageUnauthorized
: 401 error with message
For instance, if you wanted to validate a payload in a web API, you would do something like this:
@Controller("/api")
class Api {
@Get()
doSomething(context: Context, @FromQuery("id") id: string): Object {
if (id == undefined) {
throw new BadRequest("id parameter is required");
}
return {
value: id
};
}
}
##Tests execution and automation
The Kwyjibo framework includes the autogeneration of endpoints for integration tests execution, in both interactive and automatic scenarios.
###Test fixture
To add tests to you app, create a sampleTests.ts
file inside the tests
folder under the app root. The test fixture class must be decorated with @Fixture
and each test is a method inside it that has the @Test
decorator.
To do the test preparation and cleanup, you can write methods inside the fixture with the @Before
and @After
decorators.
Each test method can have either void
or Promise<void>
as its return type.
If the test finishes its execution successfully, will be considered as passed. To make a test fail, it must throw an exeption, or reject the returned promise.
A Test fixture example:
import * as K from "kwyjibo"
@K.Fixture()
export default class Fixture {
@K.Before()
prepare(): void {
// this method will run before the tests
}
@K.Test("A test that passes")
test1(): void {
// this test will pass
}
@K.Test("A test that fails")
test2(): Promise<void> {
return new Promise<void>((resolve, reject) => {
reject(new Error('failed test!'));
});
}
@K.After()
cleanUp(): void {
// this method will run after the tests
}
}
###Test runner
Then, you have to add the @TestRunner
decorator to a controller. It will scan for all the available test fixtures in the app and generate the endpoints to execute them.
A controller with the autogenerated test endpoints (test runner):
import * as K from "kwyjibo";
@K.TestRunner()
@K.Controller("/test")
export default class Test {
}
The interactive test runner will explain how to invoke the same set of tests programatically.
##Documentation generator
Kwyjibo reads all the @DocController
and @DocAction
decorators and uses that information to automatically generate documentation for your web app or API
There are two functions available to access obtain the generated documentation:
Kwyjibo.getDocs()
: returns aControllerDocNode[]
representing all the controllers documentation with their actions.Kwyjibo.getDocsAsHTML()
: returns a string with the controllers documentation as HTML
For instance, if you wanted to have a documentation endpoint for your web API, and it should only be exposed when running in a dev environment, you should create the following controller:
import * as K from "kwyjibo";
K.Dev()
K.Controller("/docs")
K.DocController("API Documentation")
export default class DocsController {
@K.Get("/")
htmlDocs(context: K.Context): string {
return K.getDocsAsHTML();
}
}