typescript
develop nodejs web applications with//----------------------------------------------// app.js//---------------------------------------------- ; ; app.listen3000; //----------------------------------------------// program.ts//---------------------------------------------- ///
install
npm install appex
contents
- getting started
- http handlers
- templating
- sitemaps
- json schema
- reflection
- developing with appex
- additional resources
- license
The following sections outline creating appex applications and configuration.
### create a applicationThe following code will create a standalone appex application and http server and listen on port 3000.
; ; app.listen3000;
note: devmode and logging are optional. however, when developing with appex, it is helpful to have these enabled.
### start up optionsappex accepts the following start up options.
; ;
The following demonstrates setting up appex on an existing nodejs http server. In this example, appex will attempt to handle incoming requests, and if appex cannot route the request, will fire the callback.
; ; ; ; server.listen3000;
appex allows developers to augment existing express / connect applications by way of middleware. The following demonstrates setting up appex as express middleware.
; ; ; app.use appex ; app.get'/', ; app.listen3000;
Like in the "running on an existing http server" example above, appex will attempt to intercept incoming requests. if appex cannot find a matching route for the request, it will automatically call the "next" function to pass the request on to the next middleware or express handler.
in addition to this, appex may also act as traditional express middleware. In the example below, a appex wildcard function is created which will match "all" incoming requests, the wildcard function simply prints hello world to the console and then calls context.next(), which passes the request on the express handler.
//----------------------------------------------// program.ts//---------------------------------------------- // http:[host]:[port]/(.*) //----------------------------------------------// app.js//---------------------------------------------- ; ; ; app.use appex ; app.get'/', ; app.listen3000;
Just like traditional express middleware, appex will also inheriate the characteristics of the request.
consider the following example in which the jade view engine is configured for use. appex will inheritate the response.render() method, which is passed to the appex handler as context.response.render()
//----------------------------------------------// app.js//---------------------------------------------- app.configure; //----------------------------------------------// program.ts//---------------------------------------------- // http:[host]:[port]/
The following sections describe how to create http accessible handlers with appex.
### contextAll appex functions are passed a application context object as their first argument. The app context object encapulates the http request and response objects issued by the underlying http server, as well as additional objects specific to appex. These are listed below:
// the app context
it is possible to extend the default objects passed on the context by adding them on the appex startup options. The following will attach the async module to the context.
//----------------------------------------------// app.js//---------------------------------------------- ; ; app.listen3000; //----------------------------------------------// program.ts//----------------------------------------------
The appex request is a nodejs http request issued by the underlying node http server. appex extends the request with convenience methods for reading http request data. These are outlined below.
reading a posted string.
//----------------------------------------------// receive request as a string//----------------------------------------------
reading posted form data as json object.
//----------------------------------------------// receive a form post//----------------------------------------------
reading posted json data as a json object.
//----------------------------------------------// receive a json post//----------------------------------------------
note: if appex detects that express or connect middleware has already been applied to the request object, appex will use those instead.
### responseThe appex response is a nodejs http response issued by the underlying node http server. appex provides some utility methods for writing http responses. These are outlined below.
//----------------------------------------------// the nodejs response has been extended with the following// signatures.//----------------------------------------------
note: if appex detects that express or connect middleware has already been applied to for any of the following response methods, appex will use those instead.
### routingappex creates routes based on module scope and function name. consider the following:
// url: http://[host]:[port]/ // url: http://[host]:[port]/about // url: http://[host]:[port]/contact // url: http://[host]:[port]/(.*)
appex supports three function signatures for http routing (named, index and wildcard). Functions that do not apply these signatures will not be routed.
### named handlersNamed handlers resolve urls to their current module scope + the name of the function.
Named handlers require the following signature:
- name - 'anything'
- argument[0] - app context
- returns - void (optional)
// http://[host]:[port]/about // http://[host]:[port]/users/login
Index handlers resolve urls to their current module scope.
Index handlers require the following signature:
- name - 'index'
- argument[0] - app context
- returns - void (optional)
// url: http://[host]:[port]/
Wildcard handlers resolve their urls to their current module scope + url.
appex wildcard handlers allow for wildcard routing at a given module scope. Wildcard handlers support 'typed' url argument mapping, as denoted by the arguments annotation.
In addition, wildcard handlers also support optional arguments which can be specified with TypeScript's '?' on argument names.
appex wildcard handlers require the following signature:
- name - 'wildcard'
- argument[0] - app context
- argument[n] - 1 or more arguments to be mapped from the url
- returns - void (optional)
declare ; // url : http://[host]:[port]/ // url : http://[host]:[port]/(.*)
note: appex supports boolean, number, string and any annotations on wildcard arguments. if no annotation is specified, appex interprets the argument as a string. the type 'any' is also interpreted as string.
note: wildcard functions should be declared last in any module scope. this ensures other routes will be matched first.
### attributesappex supports a attribute scheme which developers can use to decorate modules and functions with declaritive metadata. appex attributes can set by calling the attribute('qualifier', data) function which is passed to the appex module on the global scope.
unlike traditional attributes (in languages like C sharp) appex attributes have a cascading behaviour which allows developers to apply metadata at a lexical scope, and have it cascade through to descendant scopes.
The following outlines this behavour.
/// attribute; // global. attribute'foo',
in addition, appex recognizes three types of attributes. developers can use these to override the default bahavour of the appex router and apply url rewriting (urls), verb matching (verbs) and middleware (use), as demonstrated below.
/// // invoke 'logger' middleware.attribute'index', // override the default route.attribute'index', // only accept GET requests.attribute'index',
appex handles http verb matching with attributes. appex will recognise the 'verbs' property applied to the attribute to match against http verbs.
attribute'index', attribute'submit',
developers can rewrite the default route given to exported functions with the 'urls' property applied to the attribute.
attribute'index',
note: url rewriting is only available on index and named routes.
note: rewriting with regular expressions is currently not supported.
### middlewareappex supports middleware with attributes. appex middleware defined with attributes allows developers to scope middleware on single functions, or entire module scopes. appex will recognise the 'use' property applied to the attribute to invoke middleware.
the following demonstrates how one might use middleware to secure a site admin.
note: middleware 'must' call next or handle the request.
declare ; declare ; // apply security middleware to admin scope.attribute'admin', // index handler has no middleware applied.
appex will only route functions prefixed with the TypeScript 'export' declarer. This rule also applied to modules. Developers can use this to infer notions of public and private at the http level.
consider the following example:
// module is not exported, and is // therefore private. // function is not exported, and // is therefore private. // function is exported, and therefore // publically accessible.
Use wildcard functions to catch unhandled routes.
// http:[host]:[port]/ // http:[host]:[port]/(.*)
Use wildcard functions with context.response.serve() to serve static content.
// http:[host]:[port]/ // http:[host]:[port]/(.*)
appex comes bundled with a built in template engine which is modelled on the Microsoft Razor templating engine. The following sections outline its use.
### overviewThe appex template engine is available to all handlers by default. it is accessible on the context.template property. the following is an example of its use.
//----------------------------------------------
// view.txt
//----------------------------------------------
<ul>
@for(var n in context.users) {
@if(context.users[n].online) {
<li>@(context.users[n].name)</li>
}
}
</ul>
//----------------------------------------------
// program.ts
//----------------------------------------------
export function index(context) {
var users = [{name:'dave' , online : true},
{name:'smith', online : true},
{name:'jones', online : false},
{name:'alice', online : true}];
var text = context.template.render('./view.txt', { users: users });
context.response.headers['Content-Type'] = 'text/html';
context.response.send(text);
}
### context
each template is passed a data context. this context allows the caller to send data to the template for rendering. the context parameter is optional. the example below is sending the users array to the template context for rendering.
export function index(context) {
var users = [{name:'dave' , online : true},
{name:'smith', online : true},
{name:'jones', online : false},
{name:'alice', online : true}];
context.response.send(context.template.render('./view.txt', { users: users }));
}
### syntax
appex templates support the following statements and syntax
if statement
if statments are supported.
@if(expression) {
some content
}
@if(a > 10) {
some content
}
@(user.loggedin) {
<span>welcome</span>
}
for statement
the following for loops are supported.
@for(var i = i; i < 100; i++) {
@(i)
}
@for(var n in list) {
@(list[n])
}
expressions
will emit the value contained.
@('hello world')
@(123)
@(some_variable)
code blocks
code blocks can be useful for adding template side rendering logic.
@{
var message = 'hello'
}
@(message)
comments
@*
this comment will not be rendered!
*@
### layouts and sections
appex templates support template inheritance.
consider the following where layout.txt defines the sections 'header' and 'content' and the view.txt overrides these sections with its own content.
//----------------------------------------------
// layout.txt
//----------------------------------------------
<html>
<head>
@section header
</head>
<body>
@section content {
<span>some default content</span>
}
</body>
</html>
//----------------------------------------------
// view.txt
//----------------------------------------------
@layout 'layout.txt'
@section header {
<title>my page</title>
}
@section content {
<p>overriding the layout.txt content section.</p>
<ul>
@for(var n in context.users) {
@if(context.users[n].online) {
<li>@(context.users[n].name)</li>
}
}
</ul>
}
//----------------------------------------------
// program.ts
//----------------------------------------------
export function index(context) {
var users = [{name:'dave' , online : true},
{name:'smith', online : true},
{name:'jones', online : false},
{name:'alice', online : true}];
var text = context.template.render('./view.txt', { users: users });
context.response.send(text);
}
note : when specifying a layout, the view will only render content within the layouts section placeholders.
### renderappex templates also allow for partial views with the @render statment. consider the following which renders the nav.txt file into the layout.txt file.
//----------------------------------------------
// nav.txt
//----------------------------------------------
<ul>
<li>home</li>
<li>about</li>
<li>contact</li>
</ul>
//----------------------------------------------
// layout.txt
//----------------------------------------------
<html>
<head>
@section header
</head>
<body>
@render 'nav.txt'
@section content {
<span>some default content</span>
}
</body>
</html>
//----------------------------------------------
// view.txt
//----------------------------------------------
@layout 'layout.txt'
@section header {
<title>my page</title>
}
@section content {
<p>overriding the layout.txt content section.</p>
<ul>
@for(var n in context.users) {
@if(context.users[n].online) {
<li>@(context.users[n].name)</li>
}
}
</ul>
}
//----------------------------------------------
// program.ts
//----------------------------------------------
export function index(context) {
var users = [{name:'dave' , online : true},
{name:'smith', online : true},
{name:'jones', online : false},
{name:'alice', online : true}];
var text = context.template.render('./view.txt', { users: users });
context.response.send(text);
}
### caching and devmode
appex template content is not cached (the implementor is expected to handle their own caching) however the generated template code is.
appex templates do inheriate the behaviour of the appex 'devmode' option. setting devmode to 'true' will cause template code to be reloaded from disk and code generated with each request. setting devmode to false will load content from disk on first request, and cache the generated template code in memory for the lifetime of the application.
in addition to this, a implementation where the devmode is false can override the caching behaviour with the following.
appex is able to derive sitemap metadata automatically from http endpoints created with typescript modules and functions. This metadata is useful to generate sitemap.xml files, as well as helping to create site navigation links when combined a template engine.
### generate sitemapappex sitemaps can be obtained from the context.sitemap property.
Additionally, it may be helpful to isolate branches of the sitemap with the context.sitemap.get([qualifier]) function. as demonstrated below.
each sitemap node contains the attribute applied to the handler for which the node applies. With this developers can apply custom metadata for a given node. as demonstrated below.
declare ; attribute // global attribute'index', attribute'about', attribute'sitemap',
visiting /sitemap will output the following.
appex provides functionality for generating json schemas from TypeScript classes and interfaces as well as tools for validating json data.
### generating schemaThe following demonstrates generating json schema from the following class hierarchy.
which generates the following json schema.
a quick note...
when generating schema from classes:
- only public class variables will be emitted.
- all properties will be marked as "required".
when generating schema from interfaces:
- all properties will be emitted.
- all properties will be marked as "required" unless modified with '?'.
appex supports json schema validation from class and interface definitions. consider the following...
will output the following.
For those using appex for web services, developers can leverage appex json schema generation to generate endpoint metadata (think wsdl). Consider the following which leverages both appex schema generation and attributes to produce a metadata endpoint consumers of your api can use to see what data the endpoint http://example.com/customer/create accepts and returns.
attribute'metadata',
which outputs the following.
tip: use the appex sitemap metadata to produce a metadata endpoint for all service methods in your application.
## reflectionappex provides a reflection api derived from TypeScript's type system that developers can leverage to reflect type information declared throughout their appex modules.
the following section outlines how to use the reflection api.
### reflect everythingthe appex reflection api is passed on the context.module.reflection property and is available to all appex handler methods. The following code will JSON serialize everything declared in your appex project and write it to the http response.
In typical scenarios, developers will want to leverage reflection meta data to generate service contacts and client side models. the reflection api lets you access meta data for the following types declared in your project.
- modules
- imports
- classes
- interfaces
- functions
- variables
to access specific type metadata, use the reflection.get([qualifier]) method, as demonstrated below.
and methods..
....and variables...
;
This section outlines development with appex.
### appex.d.ts declarationIf you develop on a TypeScript complicant editor (one that supports TS 0.9), appex comes bundled with a declaration file you can reference in your project. If installing appex via npm, your reference should be as follows.
///
By referencing this in your project, you get the benefits of code completion and static type checking against both appex, and the nodejs core.
Additional declaration files may be obtained from here
### structuring projectsappex includes TypeScript's ability to reference source files with the 'reference' element. appex will traverse each source files references and include it as part of the compilation.
Developers can use this functionality to logically split source files into reusable components of functionality, as demonstrated below.
//--------------------------------------------------- // file: app.js//--------------------------------------------------- ; ; app.listen3000; //--------------------------------------------------- // file: index.ts//--------------------------------------------------- /// /// //--------------------------------------------------- // file: users.ts//--------------------------------------------------- //--------------------------------------------------- // file: pages.ts//--------------------------------------------------- // http://[host]:[port]/ // http://[host]:[port]/about // http://[host]:[port]/contact
The MIT License (MIT)
Copyright (c) 2013 Haydn Paterson (sinclair) haydn.developer@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.