micro-airtable-api
Advanced tools
Comparing version 1.0.0 to 1.1.0
# Changelog | ||
All notable changes to this project will be documented in this file. | ||
@@ -8,10 +9,17 @@ | ||
## [Unreleased] | ||
- Nothing yet. | ||
## [1.1.0] - 2018-09-14 | ||
### Added | ||
- Allow specifying permissions per-table ([#18](https://github.com/rosszurowski/micro-airtable-api/pull/18)) | ||
## [1.0.0] - 2018-09-11 | ||
### Added | ||
- npm package. | ||
### Changed | ||
- Server script to uses package export (`src/handler.js`) internally. |
{ | ||
"name": "micro-airtable-api", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"main": "index.js", | ||
@@ -5,0 +5,0 @@ "bin": "./bin/index.js", |
102
README.md
@@ -13,10 +13,6 @@ # micro-airtable-api | ||
## Setup | ||
## Usage | ||
There are three ways to run this library and set up your own Airtable proxy: | ||
The simplest way to get started with your own Airtable proxy is via [`now`](https://now.sh/). Setup and deploy with a single command: | ||
### Now | ||
To use [`now`](https://now.sh/) and deploy with a single command: | ||
```bash | ||
@@ -31,3 +27,3 @@ $ now rosszurowski/micro-airtable-api -e AIRTABLE_BASE_ID=asdf123 -e AIRTABLE_API_KEY=xyz123 | ||
``` | ||
https://micro-airtable-api-asdasd.now.sh/v0/Table | ||
https://micro-airtable-api-asdasd.now.sh/v0/TableName | ||
``` | ||
@@ -39,3 +35,3 @@ | ||
Install the package globally and run it: | ||
If you'd like to run a proxy on a different service, you can use the `micro-airtable-api` command-line. Install the package globally and run it: | ||
@@ -46,8 +42,8 @@ ```bash | ||
> Starts server on port 3000 | ||
> micro-airtable-api listening on http://localhost:3000 | ||
``` | ||
### JS | ||
### JS API | ||
Install the package locally and pass the handler into your webserver: | ||
For more advanced configuration or to integrate with an existing http or express server, you can also install the package locally and pass the handler into your webserver: | ||
@@ -78,8 +74,82 @@ ```bash | ||
`micro-airtable-api` supports a few different options through environment variables for easy deployment. | ||
`micro-airtable-api` is configurable both through the JS API and the CLI. | ||
- **`AIRTABLE_BASE_ID` (required)** The _Base ID_ of the Airtable you want to connect to. You can find this in your [Airtable API docs](https://airtable.com/api). | ||
- **`AIRTABLE_API_KEY` (required)** Your personal account API key. You can find this in [your account settings](https://airtable.com/account). | ||
- `READ_ONLY` A shortcut flag to restrict the API to only `GET` requests. Users of the API will be able to list all records and individual records, but not create, update, or delete. | ||
- `ALLOWED_METHODS` A comma-separated list of allowed HTTP methods for this API. Use this to restrict how users can interact with your API. For example, allow creating new records but not deleting by passing in a string without the delete method: `ALLOWED_METHODS=GET,POST,PATCH`. The `READ_ONLY` flag is simply a shortcut to `ALLOWED_METHODS=GET`. Note, the `OPTIONS` method is always allowed for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) purposes. | ||
```jsx | ||
const http = require('http'); | ||
const createAirtableProxy = require('micro-airtable-api'); | ||
const config = {}; | ||
http.createServer(createAirtableProxy(config)); | ||
``` | ||
#### `config.airtableBaseId` **(required)** | ||
The _Base ID_ of the Airtable you want to connect to. You can find this in your [Airtable API docs](https://airtable.com/api). | ||
#### `config.airtableApiKey` **(required)** | ||
Your personal account API key. You can find this in [your account settings](https://airtable.com/account). | ||
#### `config.allowedMethods` | ||
An array of HTTP methods supported by the API. Use this to restrict how users can interact with your API. Defaults to all methods. | ||
This maps directly to operations on Airtable: | ||
- `GET` allows reading lists of records or individual records | ||
- `POST` allows creating new records | ||
- `PATCH` allows updating existing records | ||
- `DELETE` allows removing records. | ||
To create a read-only API, to use as a CMS: | ||
```jsx | ||
createAirtableProxy({ | ||
airtableBaseId: '...', | ||
airtableApiKeyId: '...', | ||
allowedMethods: ['GET'], | ||
}); | ||
``` | ||
To create a write-only API, to use for collecting survey responses: | ||
```jsx | ||
// A write-only API (eg. surveys) | ||
createAirtableProxy({ | ||
airtableBaseId: '...', | ||
airtableApiKeyId: '...', | ||
allowedMethods: ['POST'], | ||
}); | ||
``` | ||
You can set table-specific permissions by passing in an object with table names as the keys. | ||
If you were setting up a blog through Airtable, you could do the following: | ||
```jsx | ||
createAirtableProxy({ | ||
airtableBaseId: '...', | ||
airtableApiKeyId: '...', | ||
allowedMethods: { | ||
'Blog Posts': ['GET'], | ||
'Blog Comments': ['POST', 'PATCH', 'DELETE'], | ||
}, | ||
}); | ||
``` | ||
Note, the `OPTIONS` method is always allowed for [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) purposes. | ||
### CLI Options | ||
The CLI exposes the above configuration options through environment variables for easy deployment. | ||
```bash | ||
$ AIRTABLE_BASE_ID=asdf123 AIRTABLE_API_KEY=xyz123 micro-airtable-api | ||
``` | ||
- **`AIRTABLE_BASE_ID` (required)** Same as `config.airtableBaseId` above | ||
- **`AIRTABLE_API_KEY` (required)** Same as `config.airtableApiKey` above | ||
- `ALLOWED_METHODS` Similar to `config.allowedMethods` above, except a comma-separated list instead of an array. For example, allow creating new records but not deleting by passing in a string without the delete method: `ALLOWED_METHODS=GET,POST,PATCH`. The CLI does not support table-specific permissions. Use the JS API if this is something you need. | ||
- `READ_ONLY` A shortcut variable to restrict the API to only `GET` requests. Equivalent to `ALLOWED_METHODS=GET`. Users of the API will be able to list all records and individual records, but not create, update, or delete. | ||
- `PORT` Sets the port for the local server. Defaults to `3000`. | ||
@@ -86,0 +156,0 @@ |
@@ -0,1 +1,3 @@ | ||
const { isObject } = require('./utils'); | ||
const invariant = (condition, err) => { | ||
@@ -27,4 +29,6 @@ if (condition) { | ||
invariant( | ||
Array.isArray(config.allowedMethods), | ||
new TypeError('config.allowedMethods must be an array') | ||
Array.isArray(config.allowedMethods) || | ||
isObject(config.allowedMethods) || | ||
config.allowedMethods === '*', | ||
new TypeError('config.allowedMethods must be an array or object') | ||
); | ||
@@ -31,0 +35,0 @@ |
@@ -34,4 +34,4 @@ const getConfig = require('./config'); | ||
}); | ||
}).toThrow(/allowedMethods must be an array/i); | ||
}).toThrow(/allowedMethods must be an array or object/i); | ||
}); | ||
}); |
@@ -5,4 +5,5 @@ const parse = require('url').parse; | ||
const getConfig = require('./config'); | ||
const { isObject, compact } = require('./utils'); | ||
const ALLOWED_HTTP_HEADERS = [ | ||
const allowedHttpHeaders = [ | ||
'Authorization', | ||
@@ -18,4 +19,2 @@ 'Content-Type', | ||
const match = route('/:version/*'); | ||
const writeError = (res, status, code, message) => { | ||
@@ -52,2 +51,55 @@ res.writeHead(status, { 'Content-Type': 'application/json' }); | ||
const match = route('/:version/:tableName/:recordId?'); | ||
const parseUrl = (originalUrl, airtableBaseId) => { | ||
const components = parse(originalUrl); | ||
const params = match(components.pathname); | ||
if (params === false) { | ||
const originalPath = components.path; | ||
return { proxyUrl: originalPath, tableName: false }; | ||
} | ||
const proxyUrl = | ||
'/' + | ||
compact([ | ||
params.version, | ||
airtableBaseId, | ||
params.tableName, | ||
params.recordId, | ||
components.search, | ||
]).join('/'); | ||
return { | ||
proxyUrl, | ||
tableName: params.tableName, | ||
}; | ||
}; | ||
const allMethods = ['GET', 'PUT', 'POST', 'PATCH', 'DELETE']; | ||
const getAllowedMethods = (config, tableName) => { | ||
let allowedMethods = []; | ||
if (!tableName) { | ||
allowedMethods = allMethods; | ||
} else { | ||
const hasRouteSpecificConfig = isObject(config.allowedMethods); | ||
const configAllowedMethods = hasRouteSpecificConfig | ||
? config.allowedMethods[tableName] || [] | ||
: config.allowedMethods; | ||
allowedMethods = | ||
configAllowedMethods === '*' ? allMethods : configAllowedMethods; | ||
} | ||
return ['OPTIONS', ...allowedMethods]; | ||
}; | ||
const isAllowed = (allowedMethods, method) => { | ||
if (Array.isArray(allowedMethods) && allowedMethods.includes(method)) { | ||
return true; | ||
} | ||
return false; | ||
}; | ||
module.exports = options => { | ||
@@ -61,12 +113,11 @@ const config = getConfig(options); | ||
const { proxyUrl, tableName } = parseUrl(req.url, config.airtableBaseId); | ||
const allowedMethods = getAllowedMethods(config, tableName); | ||
req.url = proxyUrl; | ||
res.setHeader('Access-Control-Allow-Origin', '*'); | ||
res.setHeader('Access-Control-Request-Method', '*'); | ||
res.setHeader( | ||
'Access-Control-Allow-Methods', | ||
['OPTIONS', ...config.allowedMethods].join(',') | ||
); | ||
res.setHeader( | ||
'Access-Control-Allow-Headers', | ||
ALLOWED_HTTP_HEADERS.join(',') | ||
); | ||
res.setHeader('Access-Control-Allow-Methods', allowedMethods.join(',')); | ||
res.setHeader('Access-Control-Allow-Headers', allowedHttpHeaders.join(',')); | ||
@@ -80,3 +131,3 @@ if (method === 'OPTIONS') { | ||
if (!config.allowedMethods.includes(method)) { | ||
if (!isAllowed(allowedMethods, method)) { | ||
writeError( | ||
@@ -86,3 +137,3 @@ res, | ||
'Method Not Allowed', | ||
`This API does not allow '${req.method}' requests` | ||
`This API does not allow '${method}' requests for '${tableName}'` | ||
); | ||
@@ -92,16 +143,4 @@ return; | ||
const originalPath = parse(req.url).path; | ||
const params = match(originalPath); | ||
const rest = params[0] || ''; | ||
let path = originalPath; | ||
if (params !== false) { | ||
path = `/${params.version}/${config.airtableBaseId}/${rest}`; | ||
} | ||
req.url = path; | ||
proxy.web(req, res); | ||
}; | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
16707
14
284
159