mongo-rest-router
Version:
Exposes a Mongo collection via a REST API
1 lines • 31.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/mongo-rest-router.ts","../src/handle-validation-error.ts","../src/with-db.ts","../src/json-patch-schema.ts","../src/apply-patch-request.ts","../src/get-validate.ts"],"sourcesContent":["import { Request, Response, Router, RouterOptions, json } from 'express'\nimport { JSONSchemaType, ErrorObject } from 'ajv'\nimport q2m from 'query-to-mongo'\nimport { Db, FindOptions, MongoClient, ObjectId } from 'mongodb'\nimport { ValidationError, handleValidateError } from './handle-validation-error'\nimport { withDb } from './with-db'\nimport { applyPatchRequest } from './apply-patch-request'\nimport { getValidate } from './get-validate'\n\ninterface DateFields {\n added?: string\n lastModified?: string\n deleted?: string\n}\n\nconst NotFoundMessage = 'An entry with that id could not be found.'\n\nconst idPath = '/:id([0-9a-fA-F]{24})' // 24-hex-digits\n\ntype DbResolver = ()=>Db\nexport interface MongoRestRouterOptions extends RouterOptions {\n db?: Db | DbResolver | string\n /** A list of methods to provide. Provide all if unset. */\n methods?: ('GET'|'POST'|'PUT'|'PATCH'|'DELETE')[],\n sort?: object,\n noGetSearch?: boolean\n noPostBulk?: boolean\n resultsField?: string\n noArchive?: boolean\n noManagedDates?: boolean\n dateFields?: DateFields\n}\n\n/**\n * A function to expose a Mongo Collection as a REST API.\n * - GET '/' - returns an object with `count` and results of search in a field named the same as the collection.\n * - GET '/:id'\n * - POST '/' - return either `insertedId` or a list of ids as `insertedIds`, if an array is posted\n * - PUT '/:id'\n * - PATCH '/:id' - expects a JSON Patch definition\n * - DELETE '/:id'\n * \n * @param {string} collection name of collection\n * @param {JSONSchemaType} schema a JSON Schema definition\n * @param {(Db|DbResolver)} options.db Mongo database. Uses req.locals.db if unset.\n * @param {('GET'|'POST'|'PUT'|'PATCH'|'DELETE')[]} options.methods List of methods to provide. Provides all if unset.\n * @param {object} options.sort the sorting to unless overridden by query parameters\n * @param {boolean} options.noGetSearch Do not provide the GET '/' route for searching.\n * @param {boolean} options.noPostBulk Do not allow an array to be provided to the POST method.\n * @param {string} options.resultsField Use this instead of the collection name as the search results field.\n * @param {boolean} options.noArchive Don't set the deleted property upon first DELETE. Remove immediately.\n * @param {boolean} options.noManagedDates Don't set date tracking fields: added, lastModified, or deleted.\n * @param {string} options.dateFields.added Use this instead of 'added' for tracking POST operations.\n * @param {string} options.dateFields.lastModified Use this instead of 'lastModified' for tracking last PUT and PATCH operations.\n * @param {string} options.dateFields.deleted Use this instead of 'deleted' for tracking DELETE operations.\n * \n * @returns {Router} an express router that exposes the collection via a REST API.\n * \n * @example\n * const BookSchema = {type: \"object\", properties { title: { type: \"string\", ...}}}\n * const booksAPI = MongRestRouter('books', BookSchema)\n * const app = express()\n * app.use('/api/v1/books', booksAPI)\n */\nexport const MongoRestRouter = <T extends object>(collection:string, schema:JSONSchemaType<T>, options={} as MongoRestRouterOptions): Router => {\n const { db, methods, sort, noGetSearch: noSearch, noPostBulk, resultsField, noArchive, noManagedDates, dateFields:dateFieldOverrides } = options\n const dateFields = { added:'added', lastModified:'lastModified', deleted:'deleted', ...dateFieldOverrides}\n const router = Router(options)\n router.use(json()) // body will be an object\n router.use(withDb(db))\n\n const {validate, validateBulk} = getValidate(schema, {dateFields})\n\n if (!methods || methods.includes('GET')) {\n /* ----------------------------------------\n * GET / - retrieve a list of entries by search criteria\n * ----------------------------------------*/\n if (!noSearch) {\n router.get('/', async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n const { criteria, options } = q2m(req.query)\n options.sort = options.sort || sort\n if (!noManagedDates) {\n (criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : false }\n }\n const result = {} as any\n result.count = await c.countDocuments(criteria)\n result[resultsField || collection] = await c.find(criteria, options as FindOptions).toArray()\n return res.send(result)\n })\n }\n\n /* ----------------------------------------\n * GET /:id - retrieve an entry\n * ----------------------------------------*/\n router.get(idPath, async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n const criteria = {'_id': new ObjectId(req.params.id)}\n if (!noManagedDates) {\n (criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : false }\n }\n const found = await c.findOne(criteria)\n if (!found) {\n res.status(404).send({error: NotFoundMessage})\n return\n }\n res.send(found)\n })\n\n if (!noArchive || noManagedDates) {\n /* ----------------------------------------\n * GET /archive - retrieve a list of deleted entries by search criteria\n * ----------------------------------------*/\n router.get(`/archive`, async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n const { criteria, options } = q2m(req.query)\n ;(criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : true }\n const result = {} as any\n result.count = await c.countDocuments(criteria)\n result[resultsField || collection] = await c.find(criteria, options as FindOptions).toArray()\n return res.send(result)\n })\n\n /* ----------------------------------------\n * GET /archive/:id - retrieve a deleted entry\n * ----------------------------------------*/\n router.get(`/archive/${idPath}`, async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n const criteria = {'_id': new ObjectId(req.params.id)}\n ;(criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : true }\n const found = await c.findOne(criteria)\n if (!found) {\n res.status(404).send({error: NotFoundMessage})\n return\n }\n res.send(found)\n })\n }\n }\n\n if (!methods || methods.includes('POST')) {\n /* ----------------------------------------\n * POST / - store an entry or bulk store many entries\n * ----------------------------------------*/\n router.post('/', async (req:Request, res:Response)=>{\n try {\n const payload = validateBulk(req.body)\n const c = req.locals.db.collection(collection)\n if (Array.isArray(payload)) {\n if (noPostBulk) {\n const e:ErrorObject = {\n keyword: '',\n instancePath: '',\n schemaPath: '',\n params: [],\n message: 'Expecting an object'\n }\n throw new ValidationError([e])\n }\n if (!noManagedDates) {\n const now = new Date()\n payload.forEach((x:{[key:string]:any}) => {\n x[dateFields.added] = now\n })\n }\n const result = await c.insertMany(payload)\n res.send({insertedIds: result.insertedIds})\n return\n }\n\n if (!noManagedDates) {\n (payload as {[key:string]:any})[dateFields.added] = new Date()\n }\n const result = await c.insertOne(payload)\n res.send({insertedId: result.insertedId})\n } catch (e) {\n handleValidateError(e, res)\n }\n })\n }\n\n if (!methods || methods.includes('PUT')) {\n /* ----------------------------------------\n * PUT /:id - update an entry\n * ----------------------------------------*/\n router.put(idPath, async (req:Request, res:Response)=>{\n try {\n const payload = validate(req.body, {allowManagedDates:true}) as {_id?:ObjectId}\n payload._id = new ObjectId(req.params.id)\n if (!noManagedDates) {\n const p = payload as {[key:string]:any}\n p[dateFields.added] = undefined // do not update\n p[dateFields.lastModified] = new Date()\n }\n const c = req.locals.db.collection(collection)\n const criteria = {'_id': payload._id}\n if (!noManagedDates) {\n (criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : false }\n }\n const result = await c.updateOne(criteria, payload)\n if (result.modifiedCount == 0) {\n res.status(404).send({error: NotFoundMessage})\n return\n }\n res.send({modifiedCount: result.modifiedCount})\n } catch (e) {\n handleValidateError(e, res)\n }\n })\n }\n\n if (!methods || methods.includes('PATCH')) {\n /* ----------------------------------------\n * PATCH /:id - update individual fields in an entry\n * ----------------------------------------*/\n router.patch(idPath, async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n const criteria = {'_id': new ObjectId(req.params.id)}\n if (!noManagedDates) {\n (criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : false }\n }\n const origObject = await c.findOne(criteria)\n if (!origObject) {\n res.status(404).send({error: NotFoundMessage})\n return\n }\n\n try {\n const newObject = applyPatchRequest(origObject, req)\n console.log('PATCH newObject', newObject)\n validate(newObject, {allowManagedDates:true})\n if (!noManagedDates) {\n (newObject as {[key:string]:any})[dateFields.lastModified] = new Date()\n }\n const result = await c.updateOne({'_id': origObject._id}, { $set: newObject })\n res.send({modifiedCount: result.matchedCount})\n } catch (e) {\n handleValidateError(e, res)\n return\n }\n })\n }\n\n if (!methods || methods.includes('DELETE')) {\n /* ----------------------------------------\n * DELETE /:id - update an entry\n * ----------------------------------------*/\n router.delete(idPath, async (req:Request, res:Response)=>{\n const c = req.locals.db.collection(collection)\n if (noArchive || noManagedDates) {\n const result = await c.deleteOne({\"_id\": new ObjectId(req.params.id)})\n res.status(200).send({deletedCount: result.deletedCount})\n return\n }\n\n // set deleted field\n const criteria = {'_id': new ObjectId(req.params.id)}\n if (!noManagedDates) {\n (criteria as {[key:string]:any})[dateFields.deleted] = { \"$exists\" : false }\n }\n const updates:{[key:string]:any} = {}\n updates[dateFields.deleted] = new Date()\n const result = await c.updateOne(criteria, { \"$set\": updates })\n res.status(200).send({deletedCount: result.modifiedCount})\n })\n\n if (!noArchive || noManagedDates) {\n router.delete(`/archive/${idPath}`, async (_req:Request, res:Response)=>{\n // TODO: implement delete from archive\n res.status(501).send({error: 'Archive not yet implemented'})\n })\n }\n }\n\n return router\n}","import { ValidationError } from \"ajv\"\nimport { Response } from \"express\"\n\nexport { ValidationError }\n\n/** Handles sending a 400 Bad Request response when catching a validation error. */\nexport const handleValidateError = (e:unknown, res:Response) => {\n if (e instanceof SyntaxError) {\n // probably a JSON.parse error\n return res.status(400).send({error: 'Invalid JSON payload', jsonParseError: e.message})\n }\n if (e instanceof ValidationError) {\n // at least one schema validation error encountered\n return res.status(400).send({error: 'Invalid payload', validationErrors: e.errors})\n }\n throw (e)\n}\n\n ","import { NextFunction, Request, Response } from \"express\"\nimport { Db, MongoClient, MongoError, MongoServerError } from \"mongodb\"\n\nconst clients:{[key:string]:MongoClient|undefined} = {}\nconst resolveDb = (db?:Db|(()=>Db)|string):Db => {\n if (typeof db == 'function') return db()\n if (db === undefined) db = process.env.MONGO_URL\n if (typeof db == 'string') {\n const client = clients[db] || new MongoClient(db)\n clients[db] = client\n return client.db()\n }\n if (db === undefined) {\n throw new Error('The withDb() function was called w/o a db. Try setting MONGO_URL.')\n }\n return db\n}\n\n// Note: typescript requires ./types/index.d.ts to exist so db shows up on express.Request\n\n/**\n * Middleware to add db instance to the Request. Uses env var MONGO_URL to define connection.\n * \n * @example\n * app.get('/api/v1/users', async (req:Request, res:Response) => {\n * res.send(await req.locals.db.collection('users').find({}).toArray())\n * })\n */\nexport const withDb = (db?:Db|(()=>Db)|string) => (req: Request, res: Response, next?: NextFunction) => {\n const mongoURL = process.env.MONGO_URL\n if (!mongoURL) throw new Error(\"MONGO_URL not set\")\n\n try {\n req.locals.db = resolveDb(db)\n } catch (e) {\n if (e instanceof MongoError) {\n // reset connection if MongoServerError or MongoError\n console.warn('Unexpected error connecting to db. Resetting the connection.', e)\n clients[mongoURL]?.close()\n clients[mongoURL] = undefined\n }\n if (e instanceof MongoServerError) {\n res.status(503).send({error: 'Mongo server is overloaded.'})\n return\n }\n if (e instanceof MongoError) {\n res.status(500).send({error: 'Unexpected Mongo error.'})\n return\n }\n console.error('Unexpected error connecting to db.', e)\n res.status(500).send({error: 'Unexpected error.'})\n }\n if (next) next()\n}","export default {\n \"$async\": true, // important so that the validate method throws errors\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [ {\n \"type\": \"object\",\n \"properties\": {\n \"op\": { \"enum\": [ \"add\", \"replace\", \"test\" ]},\n \"path\": {\n \"type\": \"string\",\n \"pattern\": \"^(\\/([^~/]|~[01])*)*$\"\n },\n \"value\": {}\n },\n \"required\": [ \"op\", \"path\", \"value\" ]\n }, {\n \"type\": \"object\",\n \"properties\": {\n \"op\": { \"enum\": [ \"remove\" ]},\n \"path\": {\n \"type\": \"string\",\n \"pattern\": \"^(\\/([^~/]|~[01])*)*$\"\n }\n },\n \"required\": [ \"op\", \"path\" ]\n }, {\n \"type\": \"object\",\n \"properties\": {\n \"op\": { \"enum\": [ \"move\", \"copy\" ]},\n \"from\": {\n \"type\": \"string\",\n \"pattern\": \"^(\\/([^~/]|~[01])*)*$\"\n },\n \"path\": {\n \"type\": \"string\",\n \"pattern\": \"^(\\/([^~/]|~[01])*)*$\"\n }\n },\n \"required\": [ \"op\", \"from\", \"path\" ]\n } ]\n }\n}","import { ValidationError } from \"./handle-validation-error\"\nimport jsonPatchSchema from './json-patch-schema'\nimport Ajv, { ErrorObject } from 'ajv'\nimport addFormats from 'ajv-formats'\nimport { ObjectId } from \"mongodb\"\nimport { Request } from \"express\"\nimport { applyPatch } from 'fast-json-patch'\n\nconst ajv = addFormats(new Ajv())\nconst validatePatchSchema = ajv.compile(jsonPatchSchema)\n\ninterface PatchTarget { [key:string]: any, _id: ObjectId }\n\nconst getPatchTarget = (o:PatchTarget, keys:string|string[]):PatchTarget|undefined => {\n if (o === undefined) return undefined\n if (!Array.isArray(keys)) {\n keys = keys.split('/').filter(x=>!!x).map(x=>x.replace('~0', '/').replace('~1', '~'))\n }\n if (keys.length > 1) {\n if (Array.isArray(o)) {\n const index = parseInt(keys[0])\n return getPatchTarget(o[index], keys.slice(1))\n }\n return getPatchTarget(o[keys[0]], keys.slice(1))\n }\n // if target is a primitive, then the last key can't be applied to it\n if (['number', 'bigint', 'string', 'boolean'].includes(typeof o)) return undefined\n return o\n}\n\ninterface HasId { [key:string]: any, _id: ObjectId }\n/**\n * Applies a PATCH request to an Mongo document.\n * \n * @param origObject An object with an `_id` field to verify the patch doesn't modify it\n * @param req An express Request object with a query and body\n * @returns the patch result\n * @throws ValidationError if the body is not a JSONPatch object, or the _id value is modified.\n */\nexport const applyPatchRequest = (origObject:HasId, req:Request) => {\n let newObject:HasId = { _id: new ObjectId() }\n const isBodyEmpty = !Object.keys(req.body).length\n if (isBodyEmpty) {\n // patch using query params\n newObject = {...origObject}\n Object.keys(req.query).filter(x=>!!x).forEach((key)=>{\n const target = getPatchTarget(newObject, key)\n const value = req.query[key]?.toString()\n\n if (target === undefined || value === undefined) return\n\n const literals = [\n ['undefined', undefined],\n ['null', null],\n ['true', true],\n ['false', false],\n ]\n const l = literals.find(([x])=>value==x)\n if (l != undefined) {\n target[key] = l[1]\n return\n }\n // quoted string\n if (value[0] == '\"' && value[0] == value[value.length-1]) {\n target[key] = value.slice(1, value.length-2)\n return\n }\n // number\n const n = parseFloat(value)\n if (n < Infinity && n > -Infinity) {\n target[key] = n\n return\n }\n // date-time\n const d = new Date(value)\n if (!isNaN(d.getTime())) {\n target[key] = d\n }\n // otherwise as-is string\n target[key] = value\n })\n } else {\n // JSON Patch\n const patch = JSON.parse(req.body)\n validatePatchSchema(req.body) // might throw ValidationError\n newObject = applyPatch(origObject, patch).newDocument\n }\n\n if (origObject._id.toHexString() != newObject._id.toHexString()) {\n const e:ErrorObject = {\n keyword: \"\",\n instancePath: \"/_id\",\n schemaPath: \"\",\n params: [],\n message: 'The _id field is read only.'\n }\n throw new ValidationError([e])\n }\n return newObject\n}","import Ajv, { JSONSchemaType } from \"ajv\"\nimport addFormats from 'ajv-formats'\n\nconst requiredIdSchema = { type: \"string\" }\nconst optionalDateSchema = { type: \"string\", format: \"date-time\", nullable: true }\n\nexport interface DateFields {\n added?: string\n lastModified?: string\n deleted?: string\n}\n\n/** ensure the id is not required and the validator will be asynchronous (ie., throw errors) */\nconst withId = <T extends object>(schema:JSONSchemaType<T>) => {\n const newSchema = {...schema} as JSONSchemaType<T>\n newSchema.properties = {...newSchema.properties, _id: requiredIdSchema, $async: true}\n newSchema.required = [...newSchema.required.filter((x:string)=>x!='_id')]\n return newSchema\n}\n\n/** allow existing object, for PUT and PATCH, to have managed date fields */\nconst withManagedDates = <T extends object>(schema:JSONSchemaType<T>, dateFields:DateFields={}) => {\n const newSchema = {...schema} as JSONSchemaType<T>\n newSchema.properties = {...newSchema.properties}\n newSchema.properties[dateFields.added || 'added'] = optionalDateSchema\n newSchema.properties[dateFields.lastModified || 'lastModified'] = optionalDateSchema\n newSchema.properties[dateFields.deleted || 'deleted'] = optionalDateSchema\n return newSchema\n}\n\ninterface Options {\n dateFields?: DateFields\n}\nexport const getValidate = <T extends object>(schema:JSONSchemaType<T>, options:Options={}) => {\n const ajv = addFormats(new Ajv())\n const { dateFields:dateFieldOverrides } = options\n const dateFields = { added:'added', lastModified:'lastModified', deleted:'deleted', ...dateFieldOverrides}\n const idSchema = withId(schema)\n const dateSchema = withManagedDates(withId(schema), dateFields)\n\n /** Validate a payload against the schema provided to MongoRestRouter. Decodes the JSON payload if it is a string. */\n const validate = (payload:unknown, options?:{allowManagedDates:boolean}):T => {\n const { allowManagedDates: isUpdate=false } = options || {}\n if (isUpdate) {\n const p = payload as {[key:string]:any}\n delete p._id\n delete p[dateFields.added]\n delete p[dateFields.lastModified]\n delete p[dateFields.deleted]\n }\n ajv.validate(schema, payload)\n return payload as T\n }\n\n /** Validate a single object or an array */\n const validateBulk = (payload:unknown):(T[]) => {\n if (Array.isArray(payload)) {\n payload.forEach(x=>!validate(x)) // throws error if any are invalid\n return payload as T[]\n }\n validate(payload as T)\n return [payload as T]\n }\n return {validate, validateBulk}\n}"],"mappings":"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","names":["ObjectId","Ajv","addFormats","ajv","addFormats","Ajv","options","options","ObjectId","result"]}