serverless-local-dev-server
Version:
Develop Alexa-Skill and HTTP functions in Serverless without deploying to AWS
114 lines (109 loc) • 4.32 kB
JavaScript
'use strict'
const Express = require('express')
const BodyParser = require('body-parser')
const path = require('path')
const dotenv = require('dotenv')
const getEndpoints = require('./endpoints/get')
class Server {
constructor () {
this.functions = []
this.staticFolder = false
this.log = console.log
this.customEnvironment = {}
// copy initial process.env
this.processEnvironment = Object.assign({}, process.env)
}
// Starts the server
start (port) {
if (this.functions.length === 0) {
this.log('No Lambdas with Alexa-Skill or HTTP events found')
return
}
this.app = Express()
this.app.use(BodyParser.json())
this.functions.forEach(func =>
func.endpoints.forEach(endpoint => this._attachEndpoint(func, endpoint))
)
if (this.staticFolder) {
this.app.use('/static', Express.static(this.staticFolder))
}
this.app.listen(port, _ => {
this.log(`Listening on port ${port} for requests 🚀`)
this.log('----')
this.functions.forEach(func => {
this.log(`${func.name}:`)
func.endpoints.forEach(endpoint => {
this.log(` ${endpoint.method} http://localhost:${port}${endpoint.path}`)
})
})
if (this.staticFolder) {
this.log(`StaticFolder`)
this.log(` GET http://localhost:${port}/static (${this.staticFolder})`)
this.app.use('/static', Express.static(this.staticFolder))
}
this.log('----')
})
}
// Sets functions, including endpoints, using the serverless config and service path
setConfiguration (serverlessConfig, servicePath) {
this.functions = Object.keys(serverlessConfig.functions).map(name => {
const functionConfig = serverlessConfig.functions[name]
const [handlerSrcFile, handlerFunctionName] = functionConfig.handler.split('.')
return {
name: name,
config: serverlessConfig.functions[name],
handlerModulePath: path.join(servicePath, handlerSrcFile),
handlerFunctionName,
environment: Object.assign({}, serverlessConfig.provider.environment, functionConfig.environment, this.customEnvironment)
}
}).map(func =>
Object.assign({}, func, { endpoints: getEndpoints(func) })
).filter(func =>
func.endpoints.length > 0
)
if (serverlessConfig.custom.localDevStaticFolder) {
this.staticFolder = path.join(servicePath, serverlessConfig.custom.localDevStaticFolder)
}
}
// Attaches HTTP endpoint to Express
_attachEndpoint (func, endpoint) {
// Validate method and path
/* istanbul ignore next */
if (!endpoint.method || !endpoint.path) {
return this.log(`Endpoint ${endpoint.type} for function ${func.name} has no method or path`)
}
// Add HTTP endpoint to Express
this.app[endpoint.method.toLowerCase()](endpoint.path, (request, response) => {
this.log(`${endpoint}`)
// Execute Lambda with corresponding event, forward response to Express
let lambdaEvent = endpoint.getLambdaEvent(request)
this._executeLambdaHandler(func, lambdaEvent).then(result => {
this.log(' ➡ Success')
if (process.env.SLS_DEBUG) console.info(result)
endpoint.handleLambdaSuccess(response, result)
}).catch(error => {
this.log(` ➡ Failure: ${error.message}`)
if (process.env.SLS_DEBUG) console.error(error.stack)
endpoint.handleLambdaFailure(response, error)
})
})
}
// Loads and executes the Lambda handler
_executeLambdaHandler (func, event) {
return new Promise((resolve, reject) => {
// Load local development environment variables
const localEnvironment = Object.assign({},
dotenv.config({path: '.env.local'}).parsed,
dotenv.config({path: '.local.env'}).parsed,
dotenv.config({path: 'local.env'}).parsed)
// set process.env explicitly
process.env = Object.assign({}, this.processEnvironment, func.environment, localEnvironment)
const handle = require(func.handlerModulePath)[func.handlerFunctionName]
const context = { succeed: resolve, fail: reject }
const callback = (error, result) => (!error) ? resolve(result) : reject(error)
// Execute it!
handle(event, context, callback)
})
}
}
module.exports = Server