The functionly library lets you build serverless nodejs applications in an innovative, functional, and fun by abstraction way. Use the JavaScript language and the JSON syntax to describe infrastructure and entities, service dependencies, and to implement service code. Deploy your solution to cloud providers, or run containerized on your onprem servers, or locally during development time using the functionly CLI.
Defining a rest service which listens on /hello-world:
import{FunctionalService,rest,description,param}from'functionly' @rest({path: '/hello-world'}) @description('hello world service')exportclassHelloWorldextendsFunctionalService{staticasynchandle(@paramname='world'){return`hello ${name}`}}exportconsthelloworld=HelloWorld.createInvoker()Running on localhost:
functionly startTry it on http://localhost:3000/hello-world?name=Joe
npm install functionly -gnpm install functionly- Create an empty Functionly project
- Create a Hello world service
- Create a Todo application with DynamoDB
- Run and Deploy with CLI
- AWS deployment
- Examples
It's a simple npm project.
- functionly
npm install --save functionlyFunctionly uses webpack with babel for compile the code.
- babel-core
- babel-loader
- babel-preset-functionly-aws
npm install --save-dev babel-core babel-loader babel-preset-functionly-awsDefault .babelrc
{"presets": ["functionly-aws"]}Default functionly.json
{"awsRegion": "us-east-1","main": "./src/index.js","deployTarget": "aws","localPort": 3000,"stage": "dev","watch": true,"compile": "babel-loader"}We need to create a FunctionalService to implement the business logic of hello world application
import{FunctionalService}from'functionly'exportclassHelloWorldextendsFunctionalService{staticasynchandle(){}}If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the rest decorator. We have to set the path property to define the rest endpoint. If we do not set the methods property that means it will accept GET requests. (default: methods: ['get'])
@rest({path: '/hello-world'})Define a description for the HelloWorld, which will make it easier to find in the AWS Lambda list.
@description('hello world service')Now we have to create the business logic.
import{FunctionalService,rest,description}from'functionly' @rest({path: '/hello-world'}) @description('hello world service')exportclassHelloWorldextendsFunctionalService{staticasynchandle(){return`hello world`}}We are almost done, we just have to export our service from the main file.
exportconsthelloworld=HelloWorld.createInvoker()In the handle method if you use the @param property decorator for a parameter then it resolves the value from a request context.
import{FunctionalService,rest,description,param}from'functionly' @rest({path: '/hello-world'}) @description('hello world service')exportclassHelloWorldextendsFunctionalService{staticasynchandle(@paramname='world'){return`hello ${name}`}}exportconsthelloworld=HelloWorld.createInvoker()Define a base class for FunctionalService to set basic Lambda settings in the AWS environment.
import{FunctionalService,aws}from'functionly' @aws({type: 'nodejs20.x',memorySize: 512,timeout: 3})exportclassTodoServiceextendsFunctionalService{}We need a DynamoTable, called TodoTable because we want to store todo items.
import{DynamoTable,dynamoTable,injectable}from'functionly' @injectable() @dynamo()exportclassTodoTableextendsDynamoTable{}We need to create a service to read todo items.
exportclassGetAllTodosextendsTodoService{staticasynchandle(){}}If you want your service to be accessible with a web request over a rest interface then you have to decorate it with the rest decorator. We have to set the path property to define the rest endpoint. If we do not set the methods property that means it will accept GET requests. (default: methods: ['get'])
@rest({path: '/getAllTodos'})Define a description for the TodoService, which will make it easier to find in the AWS Lambda list.
@description('get all Todo service')Now we have to create the business logic. We want to read the todo items, so we need to inject the TodoTable. Get the items from it and return from our service.
import{rest,description,inject}from'functionly' @rest({path: '/getAllTodos'}) @description('get all Todo service')exportclassGetAllTodosextendsTodoService{staticasynchandle(@inject(TodoTable)db){letitems=awaitdb.scan()return{ok: 1, items }}}We are almost done, we just have to export our service from the main file.
exportconstgetAllTodos=GetAllTodos.createInvoker()We need a service to create todo items, so let's do this. We will also define a rest endpoint and a description.
import{rest,description}from'functionly' @rest({path: '/createTodo',methods: ['post']}) @description('create Todo service')exportclassCreateTodoextendsTodoService{staticasynchandle(){}}We need some values to create a new todo item: name, description and status. Expect these with the param decorator, and it will resolve them from the invocation context.
import{rest,description,param}from'functionly' @rest({path: '/createTodo',methods: ['post']}) @description('create Todo service')exportclassCreateTodoextendsTodoService{staticasynchandle(@paramname, @paramdescription, @paramstaus){}}The business logic: save a new todo item. Inject the TodoTable and save a new todo item with the put function. We need an id for the new todo, in the example, we'll use shortid to generate them.
import{generate}from'shortid'import{rest,description,param}from'functionly' @rest({path: '/createTodo',methods: ['post']}) @description('create Todo service')exportclassCreateTodoextendsTodoService{staticasynchandle(@paramname, @paramdescription, @paramstatus, @inject(TodoTable)db){letitem={id: generate(), name, description, status }awaitdb.put({Item: item})return{ok: 1, item }}}exportconstcreateTodo=CreateTodo.createInvoker()Optional
Create two services: validate and persist todo items. Then the CreateTodo has only to call these services.
It will be an injectable service and expect the three todo values, then implement a validation logic in the service.
import{injectable,param}from'functionly' @injectable()exportclassValidateTodoextendsService{staticasynchandle( @paramname, @paramdescription, @paramstatus){constisValid=truereturn{ isValid }}}It will be an injectable service and expect the three todo values and inject a TodoTable then implement a persist logic in the service.
import{injectable,param,inject}from'functionly' @injectable()exportclassPersistTodoextendsService{staticasynchandle( @paramname, @paramdescription, @paramstatus, @inject(TodoTable)db){letitem={id: generate(), name, description, status }awaitdb.put({Item: item})returnitem}}inject the two new services(ValidateTodo, PersistTodo) and change the business logic
import{rest,description,param,inject}from'functionly' @rest({path: '/createTodo',methods: ['post']}) @description('create Todo service')exportclassCreateTodoextendsTodoService{staticasynchandle( @paramname, @paramdescription, @paramstatus, @inject(ValidateTodo)validateTodo, @inject(PersistTodo)persistTodo){letvalidateResult=awaitvalidateTodo({ name, description, status })if(!validateResult.isValid){thrownewError('Todo validation error')}letpersistTodoResult=awaitpersistTodo({ name, description, status })return{ok: 1, persistTodoResult }}}The source code of this example is available here
npm installThe CLI helps you to deploy and run the application.
- CLI install
npm install functionly -g- Create DynamoDB with Docker
docker run -d --name dynamodb -p 8000:8000 peopleperhour/dynamodb- Deploy will create the tables in DynamoDB
Note: Create the functionly.json in the project for short commands. Also, you don't have to pass all arguments.
functionly deploy localDuring development, you can run the application on your local machine.
functionly startDisclaimer: As functionly provisions AWS services, charges may apply to your AWS account. We suggest you to visit https://aws.amazon.com/pricing/services/ to revise the possible AWS costs.
Set up AWS Credentials before deployment.
Note: Create the functionly.json in the project for short commands. Also, you don't have to pass all arguments. As the
deployTargetis configured asaws(the default value configured) then the deploy command will use this as deployment target.
Functionly will create the package and deploy the application to AWS. The package is a CloudFormation template, it contains all the AWS resources so AWS can create or update the application's resources based on the template.
functionly deployCongratulations! You have just created and deployed your first
functionlyapplication!