Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</a>
</p>

Run REST APIs and other web applications using your existing [Node.js](https://nodejs.org/) application framework (Express, Koa, Hapi, Sails, etc.), on top of [AWS Lambda](https://aws.amazon.com/lambda/) and [Amazon API Gateway](https://aws.amazon.com/api-gateway/).
Run REST APIs and other web applications using your existing [Node.js](https://nodejs.org/) application framework (Express, Koa, Hapi, Sails, etc.), on top of [AWS Lambda](https://aws.amazon.com/lambda/) and [Amazon API Gateway](https://aws.amazon.com/api-gateway/) or [Azure Function](https://docs.microsoft.com/en-us/azure/azure-functions/).

```bash
npm install @vendia/serverless-express
Expand All @@ -25,7 +25,9 @@ Want to get up and running quickly? [Check out our basic starter example](exampl

If you want to migrate an existing application to AWS Lambda, it's advised to get the minimal example up and running first, and then copy your application source in.

## Minimal Lambda handler wrapper
## AWS

### Minimal Lambda handler wrapper

The only AWS Lambda specific code you need to write is a simple handler like below. All other code you can write as you normally do.

Expand All @@ -36,7 +38,7 @@ const app = require('./app')
exports.handler = serverlessExpress({ app })
```

## Async setup Lambda handler
### Async setup Lambda handler

If your application needs to perform some common bootstrap tasks such as connecting to a database before the request is forward to the API, you can use the following pattern (also available in [this example](https://github.com/vendia/serverless-express/blob/mainline/examples/basic-starter-api-gateway-v2/src/lambda-async-setup.js)):

Expand Down Expand Up @@ -70,6 +72,45 @@ function handler (event, context) {
exports.handler = handler
```

## Azure

### Async Azure Function (v3) handler wrapper

The only Azure Function specific code you need to write is a simple `index.js` and a `function.json` like below.

```js
// index.js
const serverlessExpress = require('@vendia/serverless-express')
const app = require('./app')
const cachedServerlessExpress = serverlessExpress({ app })

module.exports = async function (context, req) {
return cachedServerlessExpress(context, req)
}
```

The _out-binding_ parameter `"name": "$return"` is important for Serverless Express to work.

```json
// function.json
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"route": "{*segments}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
```

## 4.x

1. Improved API - Simpler for end-user to use and configure.
Expand Down
30 changes: 28 additions & 2 deletions __tests__/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,14 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
delete response.multiValueHeaders.etag
delete response.multiValueHeaders['last-modified']
break
case 'azureHttpFunctionV3':
expectedResponse.body = Buffer.from(samLogoBase64, 'base64')
expectedResponse.isBase64Encoded = false
expect(response.headers.etag).toMatch(etagRegex)
expect(response.headers['last-modified']).toMatch(lastModifiedRegex)
delete response.headers.etag
delete response.headers['last-modified']
break
case 'apiGatewayV2':
expect(response.headers.etag).toMatch(etagRegex)
expect(response.headers['last-modified']).toMatch(lastModifiedRegex)
Expand Down Expand Up @@ -388,7 +396,7 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo

test('set-cookie', async () => {
router.get('/cookie', (req, res) => {
res.cookie('Foo', 'bar')
res.cookie('Foo', 'bar', { domain: 'example.com', secure: true, httpOnly: true, sameSite: 'Strict' })
res.cookie('Fizz', 'buzz')
res.json({})
})
Expand All @@ -400,7 +408,7 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
const response = await serverlessExpressInstance(event)

const expectedSetCookieHeaders = [
'Foo=bar; Path=/',
'Foo=bar; Domain=example.com; Path=/; HttpOnly; Secure; SameSite=Strict',
'Fizz=buzz; Path=/'
]
const expectedResponse = makeResponse({
Expand All @@ -414,6 +422,24 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
},
statusCode: 200
})

switch (eventSourceName) {
case 'azureHttpFunctionV3':
expectedResponse.cookies = [
{
domain: 'example.com',
httpOnly: true,
name: 'Foo',
path: '/',
sameSite: 'Strict',
secure: true,
value: 'bar'
},
{ name: 'Fizz', path: '/', value: 'buzz' }
]
break
}

expect(response).toEqual(expectedResponse)
})

Expand Down
42 changes: 42 additions & 0 deletions examples/azure-http-function-v3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
bin
obj
csx
.vs
edge
Publish

*.user
*.suo
*.cscfg
*.Cache
project.lock.json

/packages
/TestResults

/tools/NuGet.exe
/App_Data
/secrets
/data
.secrets
appsettings.json

node_modules
dist

# Local python packages
.python_packages/

# Python Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
5 changes: 5 additions & 0 deletions examples/azure-http-function-v3/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"ms-azuretools.vscode-azurefunctions"
]
}
99 changes: 99 additions & 0 deletions examples/azure-http-function-v3/HttpExample/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const path = require('path')
const express = require('express')
const bodyParser = require('body-parser')
const cors = require('cors')
const compression = require('compression')
const { getCurrentInvoke } = require('../../../src/index') // require('@vendia/serverless-express')
const ejs = require('ejs').__express
const app = express()
const router = express.Router()

app.set('view engine', 'ejs')
app.engine('.ejs', ejs)

router.use(compression())
router.use(cors())
router.use(bodyParser.json())
router.use(bodyParser.urlencoded({ extended: true }))

// NOTE: tests can't find the views directory without this
app.set('views', path.join(__dirname, 'views'))

router.get('/api', (req, res) => {
const currentInvoke = getCurrentInvoke()
const { event = {} } = currentInvoke
const { requestContext = {} } = event
const { domainName = 'localhost:7071' } = requestContext
const apiUrl = `https://${domainName}`
res.render('index', { apiUrl })
})

router.get('/api/vendia', (req, res) => {
res.sendFile(path.join(__dirname, 'vendia-logo.png'))
})

router.get('/api/users', (req, res) => {
res.json(users)
})

router.get('/api/users/:userId', (req, res) => {
const user = getUser(req.params.userId)

if (!user) return res.status(404).json({})

return res.json(user)
})

router.post('/api/users', (req, res) => {
const user = {
id: ++userIdCounter,
name: req.body.name
}
users.push(user)
res.status(201).json(user)
})

router.put('/api/users/:userId', (req, res) => {
const user = getUser(req.params.userId)

if (!user) return res.status(404).json({})

user.name = req.body.name
res.json(user)
})

router.delete('/api/users/:userId', (req, res) => {
const userIndex = getUserIndex(req.params.userId)

if (userIndex === -1) return res.status(404).json({})

users.splice(userIndex, 1)
res.json(users)
})

router.get('/api/cookie', (req, res) => {
res.cookie('Foo', 'bar')
res.cookie('Fizz', 'buzz')
res.json({})
})

const getUser = (userId) => users.find(u => u.id === parseInt(userId))
const getUserIndex = (userId) => users.findIndex(u => u.id === parseInt(userId))

// Ephemeral in-memory data store
const users = [{
id: 1,
name: 'Joe'
}, {
id: 2,
name: 'Jane'
}]
let userIdCounter = users.length

// The serverless-express library creates a server and listens on a Unix
// Domain Socket for you, so you can remove the usual call to app.listen.
// app.listen(3000)
app.use('/', router)

// Export your express server so you can import it in the lambda function.
module.exports = app
16 changes: 16 additions & 0 deletions examples/azure-http-function-v3/HttpExample/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"route": "{*segments}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
7 changes: 7 additions & 0 deletions examples/azure-http-function-v3/HttpExample/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const serverlessExpress = require('../../../src/index') // require('@vendia/serverless-express')
const app = require('./app')
const cachedServerlessExpress = serverlessExpress({ app })

module.exports = async function (context, req) {
return cachedServerlessExpress(context, req)
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading