fx
fx copied to clipboard
How do I use fx to build giki.app
I found AWS lambda in July 2015 and became a big fan of Function as a Service (FaaS) since then, Three years ago I built my own FaaS framework fx at a Go Hackathon, then I published it on Hacker News, it quickly became one of Github trending repositories and pull in 700+ stars in one day.
While I was a little bloated, I struggled to find real-world scenario for it. This week I decided to become my own user of it, so I built an Web App with fx, and fx is amazingly handy in building APIs with FaaS way.
Hello World
fx is a simple tool I build to simplify the API development, let's take a look at how easy we build an API with fx.
You define an API in func.js , looks like this.
module.exports = (ctx) => {
ctx.body = 'hello world'
}
Then you can deploy your function to be a service with fx with one command.
$ fx up --name helloworld --port 3000 func.js
Let's test it with curl.
$ curl -v 127.0.0.1:8080
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 11
Content-Type: text/plain; charset=utf-8
Date: Tue, 06 Aug 2019 15:58:41 GMT
hello world
fx in fleself.com
fleself.com is a website (UI and APIs) are totally written with JavaScript/Node, all the APIs are built with fx.
Architecture
This's overall APIs code structure of fleself.com,
services
├── Makefile
├── db-migrations
├── tweets
│ ├── create
│ │ └── fx.js
│ ├── delete
│ │ └── fx.js
│ ├── query
│ └── fx.js
│
└── users
├── oauth
│ └── fx.js
├── sign
│ └── fx.js
└── update
└── fx.js
And each source code of an API is just a function, take /api/tweets/delete as example,
const { Client } = require('pg')
const jwt = require('jsonwebtoken')
const k = 'key_xxxxxxxx'
const create = async (ctx) => {
const { id } = ctx.request.body
if (!ctx.headers.authorization) {
ctx.throw(403, 'token required')
return
}
let user = null
try {
const token = ctx.headers.authorization.split(' ')[1]
user = jwt.verify(token, Buffer.from(k, 'base64'))
} catch (e) {
ctx.throw(e.status || 403, e.text)
return
}
try {
const client = new Client()
await client.connect()
const res = await client.query({
text: `DELETE FROM tweets WHERE id=$1 AND user_id=$2`,
values: [id, user.id],
})
await client.end()
ctx.body = 'deleted'
} catch (e) {
console.warn(e)
await client.end()
ctx.status = 500
}
}
module.exports = create
GitHub Actions Workflows
The whole development process is managed on GitHub, and I use GitHub Actions to do CI/CD, its GitHub Actions workflow looks like this.
name: api
on:
push:
paths:
- '.github/**'
- 'services/**'
branches:
- master
jobs:
api:
runs-on: ubuntu-latest
steps:
- name: check out
uses: actions/checkout@master
- name: use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: install SSH key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.SSH_KEY }}
name: id_rsa
known_hosts: ${{ secrets.KNOWN_HOSTS }}
- name: install fx
run: |
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
fx -v
- name: add fx host
run: |
fx infra create --name <node_name> --type docker --host root@<xxx.xxx.xxx.xxx>
fx use <node_name>
- name: deploy tweets_create
run: |
fx up -n tweetscreate -p 6000 services/tweets/create/fx.js --force
Caddy as Services Proxy
And I use Caddy as the frontend of the service, the config looks like this,
fleself.com {
tls [email protected]
gzip
log ./fleself.com.log
proxy /api/users/update 127.0.0.1:5000
proxy /api/users/login 127.0.0.1:5010
proxy /api/users/oauth 127.0.0.1:5020
proxy /api/tweets/create 127.0.0.1:6000
proxy /api/tweets/query 127.0.0.1:6010
proxy /api/tweets/update 127.0.0.1:6020
proxy /api/tweets/delete 127.0.0.1:6030
proxy /api/tweets/sync 127.0.0.1:7000
root ./fleself.com
rewrite / {
if {path} not_match ^/api
to {path} /
}
}
Just found this project. Every lambda fx runs is a docker container?
@lu-zen Yes, it's.