├── Readme.md ├── index.js ├── package.json ├── request.js └── response.js /Readme.md: -------------------------------------------------------------------------------- 1 | # Cloudflare Worker Rest Api 2 | 3 | This library helps build serverless rest api with cloudflare workers 4 | 5 | ## Description 6 | 7 | The idea behind this package was to create a library that would be as close to express framework as possible, and that would make creating Rest Apis with Workers quick and easy. 8 | 9 | ### Installing 10 | 11 | Begin by installing the package with npm. 12 | 13 | ``` 14 | npm install cloudflare-worker-rest-api --save 15 | ``` 16 | 17 | ### Example App 18 | 19 | For a fully working example check out this [project](https://github.com/rajtatata/cloudflare-worker-example-rest-api) 20 | 21 | ## How to use 22 | 23 | Import the package and initialize your app. 24 | 25 | ```js 26 | const restCfWorker = require("cloudflare-worker-rest-api"); 27 | const app = new restCfWorker(); 28 | 29 | // .... 30 | 31 | addEventListener("fetch", (event) => { 32 | event.respondWith(app.handleRequest(event.request)); 33 | }); 34 | ``` 35 | 36 | In order for cloudflare to use your app, we need to call `app.handleRequest` on the fetch event listener. 37 | 38 | ### Rest API 39 | 40 | The supported methods are POST, GET, DELETE, PATCH, PUT and ANY. 41 | 42 | ```js 43 | // supports middlewares 44 | app.use((req, res) => { 45 | // do some authenticate process 46 | req.isAuthenticated = true; 47 | }); 48 | 49 | app.get("/path-here", (req, res) => { 50 | // access query 51 | const { filter, page } = req.query(); 52 | 53 | return res.send({ status: 1, message: "Hello stranger!" }); 54 | }); 55 | 56 | // Three parameters for req.send method 57 | // First one is Response Data 58 | // Second one is Headers, by default it is set to {'content-type': 'application/json'} 59 | // Third one is Status Code, by default it is set to 200 60 | app.get("/path-here", async (req, res) => { 61 | // access header 62 | const contentType = await req.header("content-type"); 63 | if (contentType === "application/json") { 64 | return res.send({ status: 1, message: "File Created!" }, 201); 65 | } 66 | 67 | return res.send( 68 | "This is a string response", 69 | { "content-type": "text/plain" }, 70 | 200 71 | ); 72 | }); 73 | 74 | app.get("/path-here", async (req, res) => { 75 | // access header 76 | const contentType = await req.header("content-type"); 77 | if (contentType === "application/json") { 78 | return res.send({ status: 1, message: "This is a JSON response!" }); 79 | } 80 | 81 | return res.send("This is a string response", { 82 | "content-type": "text/plain", 83 | }); 84 | }); 85 | 86 | app.post("/path-here", async (req, res) => { 87 | // access body 88 | const { username } = await req.body(); 89 | 90 | if (!req.isAuthenticated) { 91 | // supports status code 92 | return res.send( 93 | // undefined to send default headers 94 | { status: 1, message: "Bro, you not supposed to be here!" }, 95 | undefined, 96 | 401 97 | ); 98 | } 99 | return res.send({ 100 | status: 1, 101 | message: "Hello stranger, why are you still here!", 102 | }); 103 | }); 104 | 105 | // supports path params 106 | app.delete("/item/:itemId", (req, res) => { 107 | const { itemId } = req.params; 108 | 109 | return res.send({ status: 1, message: `Oh no, you deleted item ${itemId}` }); 110 | }); 111 | ``` 112 | 113 | ### Routing 114 | 115 | The package also supports routers, if you want to divide your code in multiple files. 116 | 117 | ```js 118 | // ./routers/auth.js 119 | const restCfWorker = require("cloudflare-worker-rest-api"); 120 | 121 | const router = new restCfWorker(); 122 | 123 | router.post("/login", (req, res) => { 124 | return res.send({ status: 1, message: "Successfully logged in!" }); 125 | }); 126 | 127 | // export the router 128 | module.exports = router; 129 | ``` 130 | 131 | Then you can call your router file in your index file 132 | 133 | ```js 134 | const restCfWorker = require("cloudflare-worker-rest-api"); 135 | const authRouter = require("./routers/auth.js"); 136 | 137 | const app = new restCfWorker(); 138 | 139 | // use router 140 | app.use("/auth", authRouter); 141 | 142 | addEventListener("fetch", (event) => { 143 | event.respondWith(app.handleRequest(event.request)); 144 | }); 145 | ``` 146 | 147 | The login route now would be `POST /auth/login` 148 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const res = require('./response') 2 | const req = require('./request') 3 | 4 | module.exports = class App { 5 | constructor() { 6 | this.routes = [] 7 | this.middlewares = [] 8 | } 9 | 10 | async handleRequest(request) { 11 | this.response = new res(request) 12 | this.request = new req(request) 13 | 14 | let method = request.method 15 | let url = '/' + request.url.split('/').slice(3).join('/').split('?')[0] 16 | 17 | let route = this.routes.find(elem => this.routeCheck(elem.url, url) && elem.method === method) 18 | // if no route found, try to find with any method 19 | if (!route) route = this.routes.find(elem => this.routeCheck(elem.url, url) && elem.method === '*') 20 | 21 | if (route) { 22 | // run the middlewares first 23 | for (var i = 0; i < this.middlewares.length; i++) { 24 | await this.middlewares[i].callback(this.request, this.response) 25 | } 26 | 27 | // run the callback function 28 | return await route.callback(this.request, this.response) 29 | } 30 | 31 | return this.response.send({ status: 0, message: "Method not found!" }, 404) 32 | } 33 | 34 | get(url, callback) { 35 | this.routes.push({ 36 | url: url, 37 | method: 'GET', 38 | callback 39 | }) 40 | } 41 | 42 | post(url, callback) { 43 | this.routes.push({ 44 | url: url, 45 | method: 'POST', 46 | callback 47 | }) 48 | } 49 | 50 | put(url, callback) { 51 | this.routes.push({ 52 | url: url, 53 | method: 'PUT', 54 | callback 55 | }) 56 | } 57 | 58 | patch(url, callback) { 59 | this.routes.push({ 60 | url: url, 61 | method: 'PATCH', 62 | callback 63 | }) 64 | } 65 | 66 | delete(url, callback) { 67 | this.routes.push({ 68 | url: url, 69 | method: 'DELETE', 70 | callback 71 | }) 72 | } 73 | 74 | any(url, callback) { 75 | this.routes.push({ 76 | url: url, 77 | method: '*', 78 | callback 79 | }) 80 | } 81 | 82 | use(var1, var2) { 83 | if (arguments.length == 2) { 84 | this.useRouter(var1, var2) 85 | } else if (arguments.length === 1) { 86 | this.useMiddleware(var1) 87 | } 88 | } 89 | 90 | useMiddleware(callback) { 91 | arguments.length 92 | this.middlewares.push({ 93 | callback 94 | }) 95 | } 96 | 97 | useRouter(path, router) { 98 | router.routes.forEach(element => { 99 | this.routes.push({ 100 | url: path + (element.url === '/' ? '' : element.url), 101 | method: element.method, 102 | callback: element.callback 103 | }) 104 | }) 105 | 106 | router.middlewares.forEach(element => { 107 | this.middlewares.push({ 108 | callback: element.callback 109 | }) 110 | }) 111 | } 112 | 113 | routeCheck(route, requestRoute) { 114 | // implementing route params 115 | 116 | // split actual route from this.routes 117 | // and the route from the request 118 | let routeArray = route.split('/') 119 | let requestRouteArray = requestRoute.split('/') 120 | 121 | if (routeArray.length !== requestRouteArray.length) { 122 | return false 123 | } 124 | 125 | try { 126 | let flag = true 127 | // compare each element from both routes 128 | routeArray.forEach((elem, index) => { 129 | // check if we have url parameters 130 | // and if there is actually a value in request url 131 | // then insert to request.params 132 | if (elem.includes(':') && requestRouteArray[index] && requestRouteArray[index] !== "") { 133 | this.request.params[elem.substring(1)] = requestRouteArray[index] 134 | } else { 135 | if (elem !== requestRouteArray[index]) { 136 | flag = false 137 | return 138 | } 139 | } 140 | }) 141 | return flag 142 | } catch (error) { 143 | return false 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare-worker-rest-api", 3 | "version": "1.0.0", 4 | "description": "A cloudflare worker module which helps building REST Api quickly and easily, similar to express framework.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Flavio Rajta", 10 | "license": "ISC", 11 | "keywords": [ 12 | "express", 13 | "expressjs", 14 | "cloudflare", 15 | "worker", 16 | "rest", 17 | "api" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/rajtatata/cloudflare-worker-rest-api" 22 | } 23 | } -------------------------------------------------------------------------------- /request.js: -------------------------------------------------------------------------------- 1 | module.exports = class AppReq { 2 | constructor(request) { 3 | this.request = request; 4 | this.params = {}; 5 | } 6 | 7 | async body() { 8 | try { 9 | return await this.request.json(); 10 | } catch (error) { 11 | return {}; // cases when body is null, but still json in content header 12 | } 13 | } 14 | 15 | query() { 16 | try { 17 | let query = {}; 18 | let queryString = this.request.url.split("?")[1]; 19 | 20 | queryString.split("&").forEach((el) => { 21 | const temp = el.split("="); 22 | if (temp.length === 2) { 23 | query[temp[0]] = temp[1]; 24 | } 25 | }); 26 | return query; 27 | } catch (error) { 28 | return {}; 29 | } 30 | } 31 | 32 | /** 33 | * Takes name of the header and returns value of the header 34 | * @param {string} name Header Name 35 | * @returns {string} Value of Header 36 | */ 37 | async header(name) { 38 | try { 39 | const header = await this.request.headers.get(name); 40 | return header; 41 | } catch (error) { 42 | return {}; 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /response.js: -------------------------------------------------------------------------------- 1 | module.exports = class AppRes { 2 | constructor(request) {} 3 | 4 | /** 5 | * Send Response with data, headers and status code 6 | * @param {any} data data sent by user 7 | * @param {Object} headers headers object sent by user, default {'content-type': 'application/json'} 8 | * @param {boolean} status status for the response, default 200 9 | * @returns 10 | */ 11 | send(data, headers = { "content-type": "application/json" }, status = 200) { 12 | return new Response(JSON.stringify(data), { 13 | status, 14 | headers, 15 | }); 16 | } 17 | }; 18 | --------------------------------------------------------------------------------