UNPKG

mongo-rest-router

Version:

Exposes a Mongo collection via a REST API

216 lines (159 loc) 8.48 kB
# Mongo REST Router Creates an Express route that exposes a Mongo collection via a REST API. Here's the list of api methods exposed by the route: - **GET /api/v1/:collection** - List documents. Uses [query-to-mongo]{https://www.npmjs.com/package/query-to-mongo} to turn query parameters into search criteria. - **GET /api/v1/:collection/:id** - Retrieve a document. - **POST /api/v1/:collection** - Store a new document or bulk store a list of document. Response includes **insertedId** or **insertedIds** respectively. - **PUT /api/v1/:collection/:id** - Update a document. - **PATCH /api/v1/:collection/:id** - Update parts of a document using [JSON Patch][jp] or query parameters. - **DELETE /api/v1/:collection/:id** - Delete a document. Set's the deleted property, moving it to the archive, unless the **noArchive** option is set `true`. [jp]: https://jsonpatch.com/ The following _archive_ related routes are added unless **noArchive** is set `true`. Deleted documents are put in the archive (marked with a `deleted` field), not deleted immediately. - **GET /api/v1/:collection/archive** - List archived documents. Uses [query-to-mongo]{https://www.npmjs.com/package/query-to-mongo} to turn query parameters into search criteria. - **GET /api/v1/:collection/archive/:id** - Retrieve an archived documents. - **PATCH /api/v1/:collection/archive/:id** - Modify an archived documents. Useful for removing the deletedOn property, restoring the document. - **DELETE /api/v1/:collection/archive/:id** - Delete a document permanently. ## An example The following let's you expose a mongo collection **members** as a REST API. 1. Ensure the `MONGO_URL` is set as an env var (that's what the `withDb` middleware uses). For example: `mongodb://user:pass@localhost:27017/db`. An alternative is to call **MongoRestRoute** with an **options.db** defined. 2. define a schema using JSON Schema syntax: **members.ts** ```javascript export interface Member { _id: string name: string email: string address?: string phone?: string } export const memberSchema:JSONSchemaType<Member> = { type: 'object', properties: { _id: {type: 'string'}, name: {type: 'string'}, email: {type: 'string', format: 'email'}, address: {type: 'string', nullable: true}, phone: {type: 'string', nullable: true}, }, required: ['_id', 'name', 'email'], additionalProperties: false, } ``` 3. Add the route to the express app **routes.ts** ```javascript import express from 'express' import { MongoRestRoute, withDb } from 'mongo-rest-router' import { membersSchema } from './members.ts' const app = express() app.use('/api/v1/members', withDb, MongoRestRouter('members', membersSchema)) const port = 3000 app.listen(port, () => { console.info(`Example app listening on port ${port}`) }) ``` ## API ### MongoRestRouter **Parameters** - **collection** `string` name of collection - **schema** `JSONSchemaType` a JSON Schema definition - **options.db** `mongodb.Db` (Optional) Mongo database, a function to return one, or a mongo connection string. Uses `process.env.MONGO_URL` if unset. - **options.methods** `string[]` (Optional) List of methods to provide. List can include any of: `'GET'`, `'POST'`, `'PUT'`, `'PATCH'`, and `'DELETE'`. Provides all if unset. - **options.noGetSearch** `boolean` (Optional) Do not provide the GET / route for searching. - **options.noPostBulk** `boolean` (Optional) Do not allow an array to be provided to the POST method. - **options.resultsField** `string` (Optional) Use this instead of the collection name as the search result field. - **options.noArchive** `boolean` (Optional) Don't set the deletedOn property upon DELETE, remove it immediately. - **options.dateFields.createdOn** `string` (Optional) Use this instead of 'createdOn' for tracking the POST operations. - **options.dateFields.modifiedOn** `string` (Optional) Use this instead of 'modifiedOn' for tracking PUT and PATCH operations. - **options.dateFields.deletedOn** `string` (Optional) Use this instead of 'deletedOn' for tracking DELETE operations. **Returns** - an `express.Router` that exposes the collection via a REST API. ## Additional APIs The following functions may be useful when implementing your own business logic around create or update operations (POST, PUT, PATCH). ### applyPatchRequest A method that applies a PATCH request to an object. The request can be ether a [JSON Patch][jp] payload or query parameters. Will throw a **ValidationError** if the payload is not a [JSON Patch][jp]. **Example** ```javascript router.patch(`/api/v1/example`, (req:Request, res:Response)=>{ const origObject = { /* ... get the original object */ } try { const patched = applyPatchRequest(origObject, req) // ... do something with the patched object ... maybe validate it? } catch (e) { handleValidationError(e, res) } }) ``` ### getValidate Returns two functions, `validate` and `validateBulk`. **Parameters** - `schema` The JSON Schema to validate payloads against - `options.dateFields.added` (Optional) Use this field instead of `added` to record when the document was added to the collection. - `options.dateFields.lastModified` Use this field instead of `lastModified` to record when the document was last modified. - `options.dateFields.deleted` Use this field instead of `deleted` to record when the document was archived. **Returns** Two functions, **validate** which will return a typed object or throw a **ValidationError**, and **validateBulk** that will validate either a single object or an array of objects. ```javascript function validate<T>(payload:unknown, allowMangedDates:boolean):T function validateBulk<T>(payload:unknown, allowManagedDates):T|T[] ``` The **allowManagedDates** function will add the dateFields to the document in order to validate update payloads where those field might exist (PUT and PATCH). ### handleValidationError Sends a 400 Bad Request response when a **SyntaxError** (likely due to invalid JSON) or a **ValidationError** is thrown. Returns a payload with an `error` field and either `jsonParseError` or `validationErrors` with the details. Rethrows the error if it is not either of those. **Parameters** - `error` The caught error - `res` The response object **Example** ```javascript router.post('/api/v1/my-collection', json(), (req:Request, res:Response) => { const { validateBulk } = getValidate(myJSONSchema) try { validateBulk(req.body) // ... do something with the body } catch (e) { handleValidationError(e, res) } }) ``` ### withDb A middleware function that attaches a Mongo database instance (`mongodb.Db`) to the request at `req.locals.db`. The database connection is defined by the env var `MONGO_URL`. **Parameters** - `db` (Optional) A string (ie., a mongoDb url), a mongodb.Db, or a function that returns a Db. If db is undefined the `MONGO_URL` env var will be used. **Returns** An express middleware that sets `req.locals.db`. ### ValidationError An error thrown when the object does not conform to the schema. **Fields** - `errors` A list of **ErrorObjects** ```javascript interface ErrorObject { keyword: string // validation keyword. instancePath: string // JSON Pointer to the location in the data instance (e.g., `"/prop/1/subProp"`). schemaPath: string // JSON Pointer to the location of the failing keyword in the schema params: object // type is defined by keyword value, see below // params property is the object with the additional information about error // it can be used to generate error messages // (e.g., using [ajv-i18n](https://github.com/ajv-validator/ajv-i18n) package). // See below for parameters set by all keywords. propertyName?: string // set for errors in `propertyNames` keyword schema. // `instancePath` still points to the object in this case. message?: string // the error message (can be excluded with option `messages: false`). // Options below are added with `verbose` option: schema?: any // the value of the failing keyword in the schema. parentSchema?: object // the schema containing the keyword. data?: any // the data validated by the keyword. } ``` ## TODO - Add business logic callbacks as options