Skip to content

Commit

Permalink
fix: Add support for body parsing for Body parsing does not work when…
Browse files Browse the repository at this point in the history
… hosted on an express environment
  • Loading branch information
nicolasdao committed Dec 25, 2017
1 parent 47fdc91 commit a1a17ae
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 26 deletions.
104 changes: 98 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
<a href="https://neap.co" target="_blank"><img src="https://neap.co/img/neap_black_small_logo.png" alt="Neap Pty Ltd logo" title="Neap" align="right" height="50" width="120"/></a>

# WebFunc - Lightweight Serverless Web Framework
# WebFunc - Universal Serverless Web Framework
[![NPM][1]][2] [![Tests][3]][4]

[1]: https://img.shields.io/npm/v/webfunc.svg?style=flat
[2]: https://www.npmjs.com/package/webfunc
[3]: https://travis-ci.org/nicolasdao/webfunc.svg?branch=master
[4]: https://travis-ci.org/nicolasdao/webfunc

__*Wefunc*__ is a _universal_ web framework that uses the awesome [Zeit Now CLI](https://zeit.co/now) to manage deployments. Universal means write your code once with _webfunc_ and deploy it on any serverless platform:
- [Zeit Now](https://zeit.co/now)
- [Google Cloud Functions](https://cloud.google.com/functions/)
- [AWS Lambda](https://aws.amazon.com/lambda/) (COMING SOON...)
- [Azure Functions](https://azure.microsoft.com/en-us/services/functions/) (COMING SOON...)

You can also run it locally without any other dependencies. Run `node index.js` and that's it.

Out-of-the-box features include:
- [_Routing_](#how-to-use-it)
- [_CORS_](#cors)
- [_Environment Variables_](#adding-multiple-deployment-environments)
- 3rd party middleware.

# Table of Contents

> * [Install](#install)
Expand All @@ -16,17 +30,76 @@
> * [Authentication](#authentication)
> * [Make It Work With Express](#make-it-work-with-express)
Add CORS support and routing to Google Cloud Functions & Firebase Functions projects (AWS Lambdas coming soon).
- [Routing](#how-to-use-it)
- [CORS Support](#cors)
- [Environment Variables](#adding-multiple-deployment-environments)
# Intro



_index.js:_

```js
const { listen, serve } = require('webfunc')

const server = serve('/users/:username', (req, res, params) => res.status(200).send(`Hello env ${params.username}`))
eval(listen('server', 4000))
```

Add a __*start*__ script in your _package.json_
```js
"scripts": {
"start": "node index.js"
}
```

_Deploy locally without any other dependencies_
```
npm start
```

_Deploy to Zeit Now_
```
now
```

_Deploy to Google Cloud Function_
```
now gcp
```
> For this to work you need to configure a __*now.json*__ file as well as update the _start_ script. More detail


# Install
```
npm install webfunc --save
```

# How To Use It
# How To Use It - Show Me The Code
## Creating A REST API
### Single Endpoint Locally
_index.js:_

```js
const { listen, serve } = require('webfunc')

const server = serve('/users/:username', (req, res, params) => res.status(200).send(`Hello ${params.username}`))
eval(listen('server', 4000))
```

To run this code locally, simply run in your terminal:
```
node index.js
```

> To speed up your development, use [_hot reloading_](#easy-hot-reloading) as explained in the [Tips & Tricks](#tips-tricks) section below.
### Multiple Endpoints Locally
```js
const { listen, serve, app } = require('webfunc')

const server = serve('/users/:username', (req, res, params) => res.status(200).send(`Hello env ${params.username}`))
eval(listen('server', 4000))
```


In its simplest form, a Google Cloud Functions project looks like this:
```js
Expand Down Expand Up @@ -282,6 +355,25 @@ If you do need to allow access to anybody, then do not allow requests to send co
```
If you do need to pass authentication token, you will have to pass it using a special header(e.g. Authorization), or pass it in the query string if you want to avoid preflight queries (preflight queries happens in cross-origin requests when special headers are being used). However, passing credentials in the query string are considered a bad practice.

# Tips & Tricks
## Dev Lifecycle
### Easy Hot Reloading
While developing on your localhost, we recommend using hot reloading to help you automatically restart your node process after each change. [_node-dev_](https://github.com/fgnass/node-dev) is a lightweight development tools that watches the minimum amount of files in your project and automatically restart the node process each time a file has changed.
```
npm install node-dev --save-dev
```
Change your __*start*__ script in your _package.json_ from `"start": "node index.js"` to:
```js
"scripts": {
"start": "node-dev index.js"
}
```
Then simply start your server as follow:
```
npm start
```


# Contributing
```
npm test
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"express": "^4.16.2",
"http-errors": "^1.6.1",
"path-to-regexp": "^2.1.0",
"raw-body": "^2.3.2",
"shortid": "^2.2.8"
},
"devDependencies": {
Expand Down
42 changes: 23 additions & 19 deletions src/webfunc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
const path = require('path')
const fs = require('fs')
const shortid = require('shortid')
const getRawBody = require('raw-body')
const { getRouteDetails, matchRoute } = require('./routing')
const { app, HttpHandler } = require('./handler')
require('colors')
Expand Down Expand Up @@ -287,7 +288,7 @@ const serveHttp = (arg1, arg2, arg3) => {

return handleHttpRequest(req, res, _appconfig)
.then(() => !res.headersSent
? setResponseHeaders(res, _appconfig).then(res => httpNextRequest(req, res, Object.assign(parameters, getRequestParameters(req))))
? setResponseHeaders(res, _appconfig).then(res => getRequestParameters(req).then(paramts => httpNextRequest(req, res, Object.assign(parameters, paramts))))
: res)
.then(() => ({ req, res, ctx }))
}
Expand Down Expand Up @@ -364,24 +365,28 @@ const listen = (functionName, port) => {
}

const getRequestParameters = req => {
let bodyParameters = {}
if (req.body) {
const bodyType = typeof(req.body)
if (bodyType == 'object')
bodyParameters = req.body
else if (bodyType == 'string') {
try {
bodyParameters = JSON.parse(req.body)
}
catch(err) {
bodyParameters = {}
console.log(err)
const getBody = req.body ? Promise.resolve(req.body) : getRawBody(req)
return getBody.then(body => {
let bodyParameters = {}
if (body) {
const bodyType = typeof(req.body)
if (bodyType == 'object')
bodyParameters = req.body
else if (bodyType == 'string') {
try {
bodyParameters = JSON.parse(req.body)
}
catch(err) {
bodyParameters = {}
console.log(err)
}
}
}
}
const parameters = Object.assign((bodyParameters || {}), req.query || {})

return parameters
const parameters = Object.assign((bodyParameters || {}), req.query || {})

return parameters
})
}

const getLongestRoute = (routes=[]) => routes.sort((a,b) => b.match.length - a.match.length)[0]
Expand Down Expand Up @@ -448,9 +453,8 @@ const serveHttpEndpoints = (endpoints, appconfig) => {
if (typeof(next) != 'function')
return res.status(500).send(`Wrong argument exception. Endpoint '${httpEndpoint}' for method ${httpMethod} defines a 'next' argument that is not a function similar to '(req, res, params) => ...'.`)

const parameters = getRequestParameters(req)

return next(req, res, Object.assign(parameters, endpoint.winningRoute.parameters))
const paramts = Object.assign({}, endpoint.winningRoute.parameters)
return getRequestParameters(req).then(parameters => next(req, res, Object.assign(parameters, paramts)))
})
: res)
.then(() => ({ req, res, ctx }))
Expand Down
36 changes: 35 additions & 1 deletion test/webfunc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,40 @@ describe('webfunc', () =>
return Promise.all([result_01, result_02])
})))


/*eslint-disable */
describe('webfunc', () =>
describe('#serveHttp: 22', () =>
it('Should capture the body of a POST request and interpret it as a JSON in the params argument.', () => {
/*eslint-enable */
const req = httpMocks.createRequest({
method: 'POST',
headers: {
origin: 'http://localhost:8080',
referer: 'http://localhost:8080'
},
body: {
username: 'nic',
password: '1234'
}
})
const res = httpMocks.createResponse()
const appconfig = {
headers: {
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Origin',
'Access-Control-Allow-Origin': 'http://boris.com, http://localhost:8080',
'Access-Control-Max-Age': '1296000'
}
}
const fn = serveHttp((req, res, params) => {
res.status(200).send(`The secret password of ${params.username} is ${params.password}`)
return res
}, appconfig)
return fn(req, res).then(() => {
assert.isOk(req)
assert.equal(res.statusCode, 200)
assert.equal(res._getData(), 'The secret password of nic is 1234')
})
})))


0 comments on commit a1a17ae

Please sign in to comment.