Fast, plugin based, easy to use HTTP(s) framework for node + REST API's
var server = require('diet')
var app = new server()
app.domain('http://localhost:8000/')
app.start()
app.get('/', function($){
$.end('Hello World!')
})
// curl http://localhost:8000/ → Hello World!
// 1 line http server
node -p "require('diet').server(8000).start().get('/', function($){ $.end('yo') });"
Diet helps to build web server logic faster & eaiser, and nothing more.
Many common use cases are simplified as much as it was possible. For example, using HTTPS, hosting multiple domains, parsing headers, querystring, post body, etc...
Diet has middlewares just like express.js, the difference is in how modules are connected and used in the Router. Diet allows easier module configuration and modules can be chained. Modules designed for diet are called Plugins
.
Diet has a simple API and a wide range of Examples and Tutorials are currently in progress.
Diet's source code is only 554 lines.
Check out the wiki on github for an expanding list of helpful articles, tutorials and examples: https://github.com/adamhalasz/diet/wiki
Diet has a very powerful modular middleware structure for it's Router
. Middlewares in diet allow unlimited series of modules to work together by manipulating and passing data towards the plugin chain
.
An example GET Route
assuming db
, session
and protected
is already defined:
app.get('/settings', db, session, protected, function($){
$.data.page = 'settings'
$.html()
})
-
the first argument
of a route is the Path. Everything after the Path is a plugin middleware that forms a plugin chain -
db
instruct this route to create a database connection -
session
grabs the account from the database with the session cookie -
protected
makes sure the user is logged in. if not redirects the user toGET /accounts/login
-
the last argument
terminates the request by sending back the settings html page.
Smart & easy to use sinatra like routing.
app.get('/page/about' ...)
app.get('/users/:id/'...)
app.post('/upload'...)
Diet can host multiple websites/domains from the same node.js server.
Includes Sugar.js to extend native objects with helpful methods.
npm install diet
We have a list of plugins categorized by their purpose in the wiki: https://github.com/adamhalasz/diet/wiki/Plugin-Directory
Plugin | Category | Github Docs | Install |
---|---|---|---|
diet-ect | HTML Templates | http://git.io/q4rxng | npm install diet-ect |
diet-static | Static Files | http://git.io/TMliZw | npm install diet-static |
diet-mysql | Database | http://git.io/qDgntw | npm install diet-mysql |
diet-mongo | Database | http://git.io/7nrOOA | npm install diet-mongo |
diet-mongoose | Database | http://git.io/XG95jA | npm install diet-mongoose |
diet-mail | http://git.io/_Ymgkg | npm install diet-mail | |
diet-cookies | Cookies | http://git.io/0FIk4A | npm install diet-cookies |
diet-auth | Authentication | http://git.io/HDwgJg | npm install diet-auth |
Setup a new project in /project/index.js
// Require Diet
var server = require('diet')
// Create an App
var app = new server()
// Set Domain
app.domain('http://localhost:8000/')
// Load the HTML Template Engine
app.plugin('diet-ect', { alias: 'html' })
// Start the App
app.start()
// Listen on GET /
app.get('/', function($){
$.data.page = 'home'
$.html()
})
And write an HTML view in /project/static/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World on the <u>{{-this.page}}</u> page!</h1>
</body>
</html>
With the default node.js you would have the request
and response
arguments in your callback when using http.createServer()
.
Diet combines these two objects into a single one then adds and fixes some commonly used methods in a more meaningful way for everyday use.
The signal ($
) of each route can also be extended with Plugins
. Plugins work together seamlessly in diet because the signal ($
) object allows to pass data between plugins while keeping plugin namespaces in the signal ($
) object. When multiple plugins are assigned to a route they form a plugin chain
. The plugin chain is the key to write much less code and still achieve complex tasks.
The handy signal object can be accessed in all the Routes
and Plugins
as the first argument as you can see on the examples.
var server = require('diet')
app = new server()
app.domain('http://localhost:8000/')
app.plugin('yo')
app.start()
app.get('/ ', yo, function($){ // <-- `$` the dollar sign is the signal object
$.end($.yo.sender + ' says ' + $.yo.message) // -> John says Yo!
})
This is a list of methods that you can access and use
Method | Type | Description |
---|---|---|
$.url | Object | JSON Parsed request.url |
$.query | Object | JSON Parsed querystring |
$.params | Object | URL params from dynamic page routes like /user/:id |
$.data | Object | used in html templates and $.json() responses |
$.body | Object | JSON Parsed POST body data |
$.headers | Object | An object of all the headers. |
$.header(get, set) | Function | Get or Set Headers. The second argument is for SET, if left empty it's GET. |
$.redirect(path, statusCode) | Function | Redirect the request to a different path. path is requried, statusCode is optional. |
$.end(message) | Function | End the response with an optional message |
$.chain() | Function | Create a plugin chain manually on the fly inside a route |
$.method | String | The method of the request it's eiher GET or POST |
$.multipart | Boolean | Check if a POST request's enctype is is multipart or not. The value is a boolean: true or false . |
$.noRoute | Boolean | Check if a request has a matching registered route from app.get or app.post . The value is a boolean: true or false . |
JSON Parsed request.url
$.url = {
protocol: 'http:',
slashes: true,
host: 'example.com',
hostname: 'example.com',
href: 'http://example.com/',
pathname: '/',
path: '/' }
JSON Parsed querystring
// http://example.com/?query=value
$.query = { query: 'value' }
URL params from dynamic page routes like /user/:id
// GET http://example.com/user/eddie
$.params = { id: 'eddie' }
an object used in html templates and $.json()
responses
$.data = {}
JSON Parsed POST body data
// POST http://example.com/?message=hello
$.body = { message: 'hello' }
An object of all headers.
$.headers = { host: 'example.com',
connection: 'keep-alive',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
'accept-encoding': 'gzip,deflate,sdch',
'accept-language': 'en-US,en;q=0.8,da;q=0.6,hu;q=0.4',
cookie: 'id=999' }
Get or Set Headers. The second argument is for SET, if left empty it's GET.
$.header('cookie') // GET `cookie` -> 'id=999'
$.header('content-type', 'text/html') // SET `content-type` to `text/html`
Redirect the request to a different path. path
is requried, statusCode
is optional.
Redirect to a Path
$.redirect('/to/some/path') // internal redirect
$.redirect('http://google.com/') // external redirect
Redirect Home
// redirect to home
$.redirect('home')
// is the same as
$.redirect('/')
Redirect Back
// redirect back
$.redirect('back')
// is the same as
$.redirect($.request.headers.referer)
A string that you can use to check the method of the request. It's either GET or POST.
// GET http://example.com/
$.method // -> GET
// POST http://example.com/publish
$.method // -> POST
A function that ends the response and send back data to the requesting device.
app.get('/', function($){
$.end('end response')
})
Creates a plugin chain manually on the fly inside a route. This is useuful when you would like a create shortcut plugin that runs several other ones.
Method | Description |
---|---|
chain.plugin(pluginName, pluginPath) | Add a plugin to the chain |
chain.load(readyCallback) | Load the plugins into the chain in one-by-one after each other. readyCallback runs when all the plugins are ready. |
app.get('/', function($){
var chain = new $.chain()
chain.plugin('db', app.db)
chain.plugin('session', app.session)
chain.plugin('notifications', app.alerts)
chain.load(function(){
$.end())
})
})
Check if a POST request's enctype is is multipart or not. The value is a boolean: true
or false
.
Check if a request has a matching registered route from app.get
or app.post
. The value is a boolean: true
or false
.
Routing is the method that we describe when we pre-register a response to an anticipated incoming request from a client like a web browser, mobile or desktop app. In other words Routing is the way to prepare your app for the battle.
In this section I'm going to discuss the 2 supported http methods
, get
and post
and show examples of simple routing
and dynamic
routing using the path
argument.
Diet supports the get
and post
routing methods.
app.get('/', ...)
// curl http://example.com/
app.get('/about', ...)
// curl http://example.com/about
app.get('/article/:id', ...)
// curl http://example.com/articles/89934
app.post('/profile', ...)
// curl -X POST http://example.com/profile
Diet supports Sinatra like dynamic routing. Everything in the route path after :
is passed to $.params
with the name you provided.
As you can see :id
becomes $.params.id
which holds the value from the URL.
app.get('/user/:id', function($){
$.end('User is ' + $.params.id)
})
// http://example.com/user/adam
// -> User is adam
You can have multiple params in a route, each is assigned to the $.params
object with the parameter name you provide.
// Calendar Date
app.get('/calendar/:month/:day/:year', function($){
var message = $.params.month ',' + $.params.day + ',' + $.params.year
$.end(message)
})
// http://example.com/calendar/05/10/2014
// -> 05,10,2014
If you add ?
after the param such as :view?
it becomes optional.
app.get('/list/:view?', function($){
if($.params.view == 'gallery'){
$.end('Display #Gallery View')
} else {
$.end('Display #List View')
}
})
// http://example.com/list
// -> Display #List View
// http://example.com/list/gallery
// -> Display #Gallery View
HTTP GET promotes URI addressability so, designers should adopt it for safe operations such as simple queries.
- GET requests can be cached
- GET requests remain in the browser history
- GET requests can be bookmarked
- GET requests should never be used when dealing with sensitive data
- GET requests have length restrictions
- GET requests should be used only to retrieve data
POST is appropriate for other types of applications where a user request has the potential to change the state of the resource (or of related resources).
- POST requests are never cached
- POST requests do not remain in the browser history
- POST requests cannot be bookmarked
- POST requests have no restrictions on data length
Check out W3C for more details:
var app = new server()
app.domain('http://yourDomain.com/') // <-- ! full url required
app.start()
In some cases you might want to serve multiple domains with the same node.js application. Diet handles this beautifully.
// Diet
var server = require('diet')
// Main Domain
var app = new server()
app.domain('http://example.com/')
app.start()
app.get('/', function($){
$.end('hello world ')
})
// Sub Domain
var sub = new server()
sub.domain('http://subdomain.example.com/')
sub.start()
sub.get('/', function($){
$.end('hello world at sub domain!')
})
// Other Domain
var other = new server()
other.domain('http://other.com/')
other.start()
other.get('/', function($){
$.end('hello world at other domain')
})
Method | Description |
---|---|
app.plugin('plugin_name', config) | includes a plugin, config is an optional object |
app.start(callback) | Starts the application by loading plugins and launchung the HTTP(S) Server. callback is optional |
app.domain(url) | Specifiy the domain your server will listen to. url can be a URL string or Node.js URL Object |
app.get(path, plugins...) | Register a GET Route |
app.post(path, plugins...) | Register a POST Route |
app.path | The system path to your application. It is process.cwd() by default |
app.server | The returne value of http.createServer and https.createServer |
Example for app.domain()
// Set Domain
app.domain(url)
// `url` should be a full URL containing the protocol http or https
// and the / at the end like:
// "http://example.com/"
// `url` can also be a Node.js URL Object such as
{
hostname: "nodejs.org"
origin: "http://nodejs.org"
port: 80,
protocol: "http:"
}
Writing diet plugins are almost identical to writing node.js modules except plugins have a different require method and are directly connected to Routes.
To effectively demonstrate this we'll create a very simple bank application. The bank will have a name
and a vault
. We'll fill the vault with 6 money
when the application starts.
You can register plugins with app.plugin()
. Plugins are only initialized after app.start()
was executed.
// cd ~/yourProject
var server = require('diet')
var app = new server()
app.domain('http://localhost:8000/')
// require the bank plugin from the node_modules folder
app.plugin('bank', { name: 'John Doe', vault: 6 })
// start the application
app.start()
// cd ~/yourProject/node_modules/bank
var options = module.parent.options;
// let's see how much money the bank has
// prints -> The "John Doe Bank" has $6
console.log(' # The "'+ options.name +' Bank"'
+ ' has $'+ options.vault )
// then we need to return the plugin
module.parent.return()
Everyone who visits the app will be able to see and change the value of the vault with the deposit
and withdraw
methods.
Let's extend our bank plugin with a global
function inside exports.onload
:
// cd ~/yourProject/node_modules/bank
// Note: `module.parent.app` and `module.parent.options` are stored in memory
var options = module.parent.options;
exports.global = function($){
// this creates a new instance
// of the $.bank in visitors route
this.name = options.name
this.vault = options.vault
this.deposit = function(amount){
options.vault += parseInt(amount)
}
this.withdraw = function(amount){
options.vault -= parseInt(amount)
}
$.return(this)
}
module.parent.return()
Now that we created the plugin we can access the $.bank
in all Routes of the app
. Let's extend our index.js file with some Routes
so visitors can see
the vault and withdraw
/ deposit
virtual money.
// cd ~/yourProject/index.js
var server = require('diet')
var app = new server()
// setup domain
app.domain('http://localhost:8000/')
// use the bank plugin
app.plugin('bank', { name: 'John Doe', vault: 6 })
// start the application
app.start()
// instruct our app to
// print 'The "Y Bank" owns $X'
// upon visiting /
app.get('/', function($){
$.end('The "'+ $.bank.name +' Bank" ' + 'has $'+ $.bank.vault)
})
// instruct our app to
// deposit X amount of money into the vault
// upon visiting /banks/deposit/10
app.get('/bank/deposit/:amount', function($){
$.bank.deposit($.params.amount)
$.redirect('home')
})
// instruct our app to
// withdraw X amount of money from the vault
// upon visiting /banks/withdraw/5
app.get('/bank/withdraw/:amount', function($){
$.bank.withdraw($.params.amount)
$.redirect('home')
})
And we're done! Navigate to your project's folder in your terminal and run the application:
# bash screen 1
cd ~/yourProject/
node index.js
Now open another terminal screen and let's test the app:
# bash screen 2
curl "http://localhost:8000/"
// The "John Doe Bank" has $6
curl -L "http://localhost:8000/bank/deposit/8"
// The "John Doe Bank" has $14
curl -L "http://localhost:8000/bank/widthraw/4"
// The "John Doe Bank" has $10
Sweet! I admit it's a pretty retarded bank app, but hey I hope at least you've got some of the ideas behind the global plugins
.
Local plugins are only used in specified routes. Let's create a currency converter
plugin that we'll use in the /convert/:currency
route.
The converter plugin will use $.bank.vault
from the bank
plugin. We can do this because global plugins always load first, before the local plugins.
// cd ~/yourProject/node_modules/convert
exports.local = function($){
// The bank's balance is in USD
// I'll use today's (September 19, 2014)
// exchange rate
if($.params.currency == 'EUR'){
this.amount = $.bank.vault * 0.77;
this.symbol = '€';
} else if ($.params.currency == 'GBP'){
this.amount = $.bank.vault * 0.60;
this.symbol = '£'
}
$.return(this)
}
module.parent.return()
Now let's add the convert
plugin and the route /convert/:currency
to index.js
// use the convert plugin with a reference
var convert = app.plugin('convert')
// instruct our app to
// print 'The "Y Bank" owns X(symbol) Z.'
// upon visiting /
app.get('/convert/:currency', convert, function($){
$.end('The "'+ $.bank.name +' Bank" '
+ 'has '+ $.convert.symbol + $.convert.amount)
})
Voila! Let's test this. First run the app:
# bash screen 1
cd ~/yourProject/
node index.js
Then test the new Plugin & Route:
# bash screen 2
curl "http://localhost:8000/convert/EUR"
// The "John Doe Bank" has €7.7
curl "http://localhost:8000/convert/GBP"
// The "John Doe Bank" has £6
Upcoming updates and features: https://github.com/adamhalasz/diet/wiki/Todos
(The MIT License)
Copyright (c) 2014 Halász Ádám mail@adamhalasz.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.