{"id":18084821,"url":"https://github.com/coderofsalvation/gexpress","last_synced_at":"2025-04-12T20:09:07.315Z","repository":{"id":146965209,"uuid":"138007198","full_name":"coderofsalvation/Gexpress","owner":"coderofsalvation","description":"Express middleware for google appscript (build NODEJS-like applications) + generated api-client","archived":false,"fork":false,"pushed_at":"2020-05-29T17:23:17.000Z","size":1932,"stargazers_count":74,"open_issues_count":0,"forks_count":11,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-12T20:08:44.915Z","etag":null,"topics":["appscript","express","express-middleware","google","google-apps-script","javascript"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coderofsalvation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"custom":"https://gumroad.com/l/hGYGh"}},"created_at":"2018-06-20T09:04:24.000Z","updated_at":"2025-03-10T14:30:05.000Z","dependencies_parsed_at":"2023-05-05T22:30:25.351Z","dependency_job_id":null,"html_url":"https://github.com/coderofsalvation/Gexpress","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2FGexpress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2FGexpress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2FGexpress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coderofsalvation%2FGexpress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coderofsalvation","download_url":"https://codeload.github.com/coderofsalvation/Gexpress/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625493,"owners_count":21135513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appscript","express","express-middleware","google","google-apps-script","javascript"],"created_at":"2024-10-31T15:08:18.858Z","updated_at":"2025-04-12T20:09:07.277Z","avatar_url":"https://github.com/coderofsalvation.png","language":null,"funding_links":["https://gumroad.com/l/hGYGh"],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"gexpress.png\"/\u003e\n\n## Usage\n\nSurf to [script.google.com](https://script.google.com), create a script, and copy/paste this into Code.gs:\n\n```js\nvar app   = new Gexpress.App()\nvar cache = CacheService.getScriptCache()\n\ncache.put(\"/hello\", JSON.stringify({date: new Date()}) )\n\napp.use(function(req,res,next){\n  req.user = {email:Session.getActiveUser().getEmail()}\n  next()\n})\n\napp.get('/hello',function(req,res,next){\n  res.set('content-type','application/json')\n  res.send( cache.get('/hello') )\n  res.end()\n},true)\n\napp.get('/client.js', app.client() )\n\napp.get(/.*/, function(req,res,next){\n  res.set('content-type','text/html')\n  res.send(\"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eHello\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\") // see docs for template-usage \u0026 banner-removal\n  res.end()\n})\n\n// this hooks Gexpress into appscript \nfunction doGet(e) { return app.doGet(e)  }\nfunction doPost(e){ return app.doPost(e) }\n```\n\u003e .put() .post() .delete() and .options() are also supported (see virtual endpoints)\n\nThis creates these urls:\n\n* anonymous: [?path=/hello](https://script.google.com/macros/s/AKfycbziqV-T6HudofXLfmMoQS4_AL68f_x6CUlJYIzs2Q-SYaHoWBgq/exec?path=/hello)\n* anonymous: [?path=/client.js](https://script.google.com/macros/s/AKfycbziqV-T6HudofXLfmMoQS4_AL68f_x6CUlJYIzs2Q-SYaHoWBgq/exec?path=/client.js)\n* authenticated: [/hello](https://script.google.com/macros/s/AKfycbziqV-T6HudofXLfmMoQS4_AL68f_x6CUlJYIzs2Q-SYaHoWBgq/exec/hello)\n* authenticated: [/client.js](https://script.google.com/macros/s/AKfycbziqV-T6HudofXLfmMoQS4_AL68f_x6CUlJYIzs2Q-SYaHoWBgq/exec/client.js)\n\n\u003e click the urls to see live demo output\n\n## Include the library\n\nAdd `1Lm_jNmD2FWYF-Kgj7AdHVvLEVXZ4c5AXwzd1KJSb48scn0HLBq64um7S` to your libraries (Resources \u003e Libraries).\n\n\u003cimg src='include.gif'/\u003e\n\n\u003e NOTE: please make sure you select the latest version of the library\n\n## Debugging\n\nUse [BetterLog](https://github.com/peterherrmann/BetterLog) to easily log into a spreadsheet (because Logger.log does not always work inside `doGet()` and `doPost()`)\n\n    Logger = BetterLog.useSpreadsheet('1gUQI4SUyQbIoNYwUHORgl') // spreadsheet id\n    Logger.log(\"hello world\")\n\n## Permissions and users\n\nMake sure you deploy with these settings:\n\n\u003cimg src='deploy.png'/\u003e\n\nYou can add google users to the appscript (share-button), **re-deploy**, and you're done.\nRules of thumb:\n\n* use the rooturl (`/exec`) for anonymous access\n* use other urls (`/exec/myadmin` e.g.) urls for authenticated access\n\nThe latter will automatically trigger login for anonymous users.\nSee an overview of (non)authenticated urls below.\n\n## RESTFUL-ish\n\nWebtraffic to Google Appscript Webapps is limited/secured in many ways.\nThis is not that bad, given that every appscript gives us:\n\n* free security + free https! =)\n\n#### Virtual CORS anonymous endpoints \n\nBy default, Gexpress exposes endpoints in a slightly different way (compared to express):\n\n| Gexpress method | Listens to webrequest(s) | Anonymous webrequest | CORS | application/json | application/javascript | text/xml | text/plain | text/html \n|-|-|-|-|-|-|-|-|-|\n| app.get('/foo',..)     | GET  /exec?path=/foo                 | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n|                        | POST /exec?path=/foo\u0026method=GET      | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.post('/foo',..)    | POST /exec?path=/foo\u0026method=POSTt     | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.put('/foo',..)     | POST /exec?path=/foo\u0026method=PUT      | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.delete('/foo',..)  | POST /exec?path=/foo\u0026method=DELETE   | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.options('/foo',..) | POST /exec?path=/foo\u0026method=OPTIONS  | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n\n\u003e ⚠ = will trigger `this application was created by another user`-banner if not logged in as appscript-owner. See chapter 'Banner 101'\n\nIf you really want 100% restful endpoints, see the following section.\n\n#### Authenticated endpoints \n\nIf you only want authenticated (gsuite) users to access your webapp (see settings in Publish \u003e Deploy as webapp), then these are the exposed endpoints:\n\n| Gexpress method | Listens to webrequest(s) | Anonymous webrequest | CORS | application/json | application/javascript | text/xml | text/plain | text/html \n|-|-|-|-|-|-|-|-|-|\n| app.get(/.*/,..)       | GET  /exec                            | ✓              | ✓ | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.get('/foo',..)     | GET  /exec/foo                        | triggers auth  |   | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.post('/foo',..)    | POST /exec/foo                        | triggers auth  |   | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.put('/foo',..)     | POST /exec/foo\u0026method=PUT             | triggers auth  |   | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.delete('/foo',..)  | POST /exec/foo\u0026method=DELETE          | triggers auth  |   | ✓ | ✓ | ✓ | ✓ | ⚠ |\n| app.options('/foo',..) | POST /exec/foo\u0026method=OPTIONS         | triggers auth  |   | ✓ | ✓ | ✓ | ✓ | ⚠ |\n\n\u003e NOTE: disable the virtual endpoints by initializing Gexpress with `new Gexpress.App({pathToQuery:false})`\n\n## Accessing data from requests \n\n| example | retrieval |\n|-|-|\n| GET /exec?path=/foo/12 | req.query.path, req.params.id |\n| GET /exec?path=/foo\u0026bar=1 | req.query.path, req.query.bar |\n| POST /exec?path=/foo\u0026bar=1 {...} | req.query.path, req.query.bar, req.body |\n| PUT /exec?path=/foo\u0026method=PUT\u0026bar=1 {...} | req.query.path, req.query.bar, req.body |\n| DELETE /exec?path=/foo\u0026method=DELETE\u0026bar=1 {...} | req.query.path, req.query.bar, req.body |\n\n\u003e use this middleware to log requests (View \u003e Logs)\n\n    app.use(function(req,res,next){\n      Logger.log( req.method+\" \"+req.url+\" \"+(req.route ? \"(\"+req.route+\")\":\"\")+\" \"+JSON.stringify(req.params) )\n      next()\n    })\n\n## Regex / Serving files / templating\n\nAppscript has builtin support for templating, here's a simple example.\nServe this `index.html`-file:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cbase target=\"_top\"\u003e\n\t\u003ctitle\u003e\u003c?= title ?\u003e\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003c?!= foo() ?\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\nWith this endpoint:\n\n\n```js\nfunction foo(id){\n  return \"Hello world \"+id\n}\n\napp.get( /.*/, function(req,res,next){ // default to homepage\n  Logger.log(\"defaulting to homepage\")\n  var html = HtmlService.createTemplateFromFile('index') // this will get the index.html-file from your appscript project\n  html.title = 'Hello'\n  res.set('content-type','text/html')\n  res.send( html.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL).getContent() )\n  res.end()\n})\n\n```\n\n## Retrieving url arguments\n\n| route                | url request | req.url value | req.route value | retrieve data | note |\n|-|-|-|-|-|-|\n| app.get('/foo')      | GET /foo?bar=1 | /foo       | /foo           | req.query.bar  |      |\n| app.get('/foo')      | GET /foo/123   | /foo       | /foo/:id       | req.params.id  | :id is automatically detected |\n| app.get('/foo/:foo') | GET /foo/123   | /foo       | /foo/:foo      | req.params.foo |  |\n\n## Generate Browser JS client \n\nGexpress can automatically generate a client (see `app.client()` above), which you can decorate further:\n\n```js\napp.put('/foo', function(req,res,next){   .... }, true)      // note: true includes endpoint into client.js\n\napp.get('/client.js', app.client( function(code){\n  return ' ' + code + ' ' \n})\n```\n\n\u003e Now insert `\u003cscript src=\"https://script.google.com/{SCRIPTID}/exec?path=/client.js\"\u003e\u003c/script\u003e` in your html`\n\nThe generated client will allow you to do this:\n\n```js\n  gclient.get('/foo'}).then( alert ).catch( alert) \n  gclient.get('/foo/123'}).then( alert ).catch( alert) \n  gclient.post('/foo',{bar:1}).then( alert ).catch( alert) \n  gclient.put('/foo/123',{bar:2}).then( alert ).catch( alert) \n  gclient.delete('/foo/123').then( alert ).catch( alert) \n```\n\nJust look at the client-source and you'll see some examples.\n\n\u003e NOTE: Always make sure you create a new deployment before testing changes. Development-urls (ending with `/dev`) do not allow POST requests (`post(),put(),delete()` in our case). This is an appscript limitation.\nHence the client will always use the `/exec`-url production url. \n\n## Generate Node.js client \n\nInstall node-tech, and download the client.js-contents of above locally (the script-tag src-url), and save it into file `client.js`.\n\n    $ npm install node-fetch --save\n    $ curl -L 'https://script.google.com/{SCRIPTID}/exec?path=/client.js' \u003e client.js\n\nThen create a file called mynodescript.js:\n```js\n    var gclient = require('./client.js')(require('node-fetch'))\n    gclient.get('/foo')\n    .then(  console.dir )\n    .catch( console.dir )\n\n    /* outputs:\n     *\n     * { limit: '3',\n     *   offset: '0',\n     *   order: 'date_modify DESC',\n     *   nitems: 3,\n     *   items:\n     *    [ { '#': '1',\n     *        name_first: '...',\n     *        name_last: '...',\n     *      }, \n     *      { '#': '45',\n     *      ....\n     */\n```\n\n\u003e Voila!\n\n## Middleware\n\n| middleware | info |\n|-|-|\n| [Gexpress-middleware-RESTsheet](https://github.com/coderofsalvation/Gexpress-middleware-RESTsheet) | exposes spreadsheet as REST endpoints |\n\n## Banner 101\n\nIn order to get rid of the (not made by google) banner, you can do 2 things:\n\n* create a google site and include the script\n* include the script as an iframe on another domain (host on gitlab/github/bitbucket page e.g.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoderofsalvation%2Fgexpress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoderofsalvation%2Fgexpress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoderofsalvation%2Fgexpress/lists"}