npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

atma-server

v0.5.22

Published

Server Application

Downloads

335

Readme

Atma Node.js Server Module

Build Status NPM version

Overview

Can be used as a Connect Middleware

This module uses:

To setup a bootstrap project use Atma.Toolkit - $ atma gen server

HttpApplication

var atma = require('atma-server');
atma
	.server
	.Application({
		base:__dirname,
		configs: '/server/config/**.yml'
	})
	.done(function(app){
		// configuration and resources are loaded
		app
			.processor({
				// pipeline is executed on every request
				before: [
					function(req, res, next){ next() },
				]
				// this pipeline is executed only if the application finds any endpoint
				// (server, handler, page, subapp)
				middleware: [
					// refer to connectjs middleware documentation
					function(req, res, next){ next() },
					require('body-parser').json(),
				],
				// otherwise, if response was not completed by any middleware or any endpoint before
				// continue with this middleware pipeline.
				after: [
					function(req, res, next){ next() },
					atma.server.middleware.static
				]
			})

			// start server, portnumber is taken from the configuration
			.listen();

		// or start the server manually:
		var server = require('http')
			.createServer(app.process)
			.listen(app.config.$get('port'));

		if (app.config.debug)
			app.autoreload(server);
	});

Configuration

appcfg module is used to load the configurations and the routings. Default path is the /server/config/**.yml.

The default configuration can be viewed here - link

Resources

scripts / styles for the NodeJS application itself and for the web pages. They are defined in:

  • config.env.both.scripts<Object|Array>

    config/env/both.yml - shared resources

  • config.env.server.scripts<Object|Array>

    config/env/server.yml - resources for the nodejs application, e.g. server side components paths.

  • config.env.client.scripts<Object|Array>, config.env.client.styles<Object|Array>

    config/env/client.yml - resources, that should be loaded on the client.

    In the DEV Mode all client-side scripts/styles/components are served to browsers without concatenation. For the production compile resources with atma custom node_modules/atma-server/tools/compile

  • Define scripts and styles for a particular page in page routing.

Routing

  • subapps config/app.yml

    subapps:
    	// all `rest/*` requests are piped to the Api Application
    	// `Api.js` should export the `atma.server.Application` instance
    	'rest': '/../Api.js'
  • handlers config/handlers.yml

    handler:
    	location: /server/http/handler/{0}.js
    	#< default
    handlers:
    	# route - resource that exports a HttpHandler
    	'/foo': 'baz'
    		# path is '/server/http/handler/baz.js'
    		# method is '*'
    
    	'$post /qux': 'qux/postHandler'
    		# path is '/server/http/handler/quz/postHander.js'
    		# method is 'POST'
  • services config/services.yml

    service:
    	location: /server/http/service/{0}.js
    	#< default
    services:
    	# route - resource that exports a HttpService @see HttpService
    	'/user': 'User'
    		# path is '/server/http/service/User.js'
    		# method is '*'
    		# futher routing is handled by the service, like '/user/:id'
  • pages config/pages.yml

    page:
    	# see default config to see the default page paths
    
    pages:
    	# route - Page Definition
    
    	/:
    		id: index #optional, or is generated from the route
    
    		template: quz #optional, or is equal to `id`
    			# path is `/server/http/page/quz/quz.mask
    		master: simple #optional, or is `default`
    			# path is `/server/http/master/simple.mask`
    
    		# optional
    		secure:
    			# optional, default - any logged in user
    			role: 'admin'
    
    		scripts:
    			# scripts for the page
    		styles:
    			# styles for the page
    
    		# any other data, which then is accessable via javascript or mask
    		# `ctx.page.data.title`
    		title: String
    
    		# rewrite the page request to some other route
    		rewrite: String
    
    		# redirect the page request to some other route
    		redirect: String

Endpoints

There are 4 types of endpoints in route lookup order

Sub Application

We support application nesting, that means you can bind another server application instance for the route e.g. /api/ and all /api/** requests are piped to the app. Each application instance has its own settings and configurations. This allows to create highly modular and composit web-applications.

Handler

To declare a Handler is as simple as to define a Class/Constructor with Deferred(Promise) Interface and process function in prototypes, like this

// server/http/handler/hello.js
module.exports = Class({
	Base: Class.Deferred,
	process: function(req, res){
		this.resolve(
			data String | Object | Buffer,
			?statusCode Number,
			?mimeType String,
			?headers Object
		);
		this.reject(error)
	}
});

To bind for a route(server/config/handlers.yml):

handler:
	location: '/server/http/handler/{0}.js'
	# <- default
handlers:
	'/say/hello': Hello
	'(\.less(\.map)?$)': LessHandler
	'(\.es6(\.map)?$)': TraceurHandler

Usually, this are the low level handlers, like 'less' preprocessor. But the interface (Deferred + process(req, res)) is same also for HttpService and HttpPage

HttpEndpoint

Class and decorators oriented HttpService

import { HttpEndpoint, deco } from 'atma-server'

@deco.route('/foo')
@deco.isAuthorized()
export default class MyEndpoint extends HttpEndpoint {

    @deco.isInRole('admin')
    async '$get /:id' (
        @deco.fromUri('id', Number) id: number
    ) {
        return service.fetch(id)
    }
}

Decorators can be applied to the class or methods

  • HttpEndpoint.isAuthorized()
  • HttpEndpoint.isInRole(...roles: string[])
  • HttpEndpoint.hasClaim(...roles: string[])
  • HttpEndpoint.origin(origin: string = "*")
  • HttpEndpoint.middleware(fn: (req, res?, params?) => Promise<any> | any | void)
  • HttpEndpoint.createDecorator(methods: ICreateDecorator)
  • HttpEndpoint.fromUri(name, Type?)
  • HttpEndpoint.fromBody(Type)

Decorators are also accessable via deco export, e.g.: deco.isAuthorized()

interface ICreateDecorator {
    forCtor (Ctor: Function, meta: IHttpEndpointMeta): Function | void;
    forMethod (Proto: any, method: IHttpEndpointMethod): IHttpEndpointMethod | void
}

HttpService

Service routes

For the route docs refer to RutaJS

Sample:

module.exports = atma.server.HttpService({
	'$get /': Function | Endpoint
	'$post /': ...
	'$get /:name(foo|bar|qux)': ...
	'$put /user': ...
})

Service endpoints

Function
atma.server.HttpService(/*endpoints*/ {
	// route:handler
	'route': function(req, res, params){
		this.resolve(/*@see Handler*/);
		this.reject(...);
	},

	'route': {
		process: function(){ ... }
	}
})
Meta - help & validation
  • help - list all endpoints of a service with there meta information. http://127.0.0.1/rest/user?help
  • validation - when sending data with post/put, httpservice will validate it before processing
    atma.server.HttpService({
    	'/route': {
    		meta: {
    			description: 'Lorem...',
    
    			/* For request validating and the documentation */
    			arguments: {
    				// required, not empty string
    				foo: 'string',
    				// required, validate with regexp
    				age: /^\d+$/,
    
    				// optional, of type 'number'
    				'?baz': 'number',
    
    				// unexpect
    				'-quz': null,
    
    				// validate subobject
    				jokers: {
    					left: 'number',
    					right: 'number'
    				},
    
    				// validate arrays
    				collection: [ {_id: 'string', username: 'string'} ]
    			},
    			// allow only properties which are listed in `arguments` object
    			strict: false,
    
    			/* Documentation purpose only*/
    			response: {
    				baz: 'string',
    				...
    			}
    		},
    		process: function(req, res, params) { ... }
    	}
    })
    • Headers Set default headers for the service
    atma.server.HttpService({
    	'/route': {
    		meta: {
    			headers: {
    				'Access-Control-Allow-Origin': '*',
    				'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
    			}
    		},
    		process: function(req, res, params) { ... }
    	}
    });
Barricades (Middlewares)
atma.server.HttpService({
	// route - Barricade (Middleware pattern)
	'/route': [
		function(req, res, params, next){
			// error example
			if (req.body.name == null){
				next('Name argument expected');
				return;
			}

			// continue
			req.name = req.body.name;
			next();

			// stop processing
			this.resolve(...);
			this.reject(...);
		},
		function(req, res, params, next){
			...
		},
		...
	],

	// same with `help`
	'/other/route': {
		meta: { ... }
		process: [
			fooFunction,
			bazFunction,
			...
		]
	}
})
Service and the application routing example
// server/http/service/time.js
module.exports = atma.server.HttpService({
	'/': function(req, res){
		this.resolve('This is a time service');
	},
	'/:transport(console|file|client)': function(req, res, params){
		var time = new Date().toString(),
			that = this;
		switch(params.transport){
			case 'console':
				console.log(' > time', time);
				this.resolve('Pushed to console');
				return;
			case 'file':
				io
					.File
					.writeAsync('someFile.txt')
					.pipe(this, 'fail')
					.done(() => {
						this.resolve('Saved to file');
					});
				return;
			case 'client':
				this.resolve(time);
				return;
		}
	}
})
# server/config/services.yml

service:
	location: /server/http/service/{0}.js'
	# <- default

services:
	'/time': time

HttpPage

HttpPage consists of 3 parts

  • Controller
  • Master View Template
  • View Template

You would rare redefine the default controller, as each Page should consist of a component composition, so that the logic could be moved to each component. We wont explain what a component is, as you should refer to MaskJS and MaskJS.Node Some things we remind:

  • Context

    { req: <Request>, res: <Response>, page: <HttpPage (current instance)> }
  • Render-mode

    mode: 'server' | 'client' | 'both' // @default is 'both'
    modeModel: 'server' // if `server` is defined, the model wont be serialized
  • Cache Each components output could be cached and the conditions could be defined.

    • byProperty: For each unique value from model or ct

Example

mask.registerHandler(':requestedUrl', Compo({
	mode: 'server:all'
	modelMode: 'server:all'
	cache: {
		byProperty: 'ctx.req.url'
	},

	onRenderStart: function(model, ctx){
		this.nodes = jmask('h4').text(ctx.req.url);
	}
}))

Going back to the HttpPage, lets start from a master view template

Master View

Refer to the layout component

Example:

// server/http/master/default.mask
layout:master #default {
	:document {

		head {
			meta http-equiv="Content-Type" content="text/html;charset=utf-8";
			meta name="viewport" content="maximum-scale=1.5, minimum-scale=.8, initial-scale=1, user-scalable=1";
			title > "Atma.js"

			atma:styles;
		}
		body {

			@placeholder #body;

			atma:scripts;
		}
	}
}

Page View

// server/http/page/hello.mask
layout:view master=default {
	@content #body {
		'Hello World'
	}
}

The routing is also made via the configuration files

# server/config/pages.yml

pages:
	'/hello':
		id: hello

Preprocessors

E.g., to use ES6 or Less files, please install server plugins

# atma.toolkit, is only a helper util to intall plugins (further on is not required)
$ npm i atma -g
$ atma plugin install atma-loader-traceur
$ atma plugin install atma-loader-less

:copyright: 2014-2015; MIT; The Atma.js Project