{"id":16834715,"url":"https://github.com/mscdex/reformed","last_synced_at":"2025-04-11T04:33:36.358Z","repository":{"id":15946061,"uuid":"18688465","full_name":"mscdex/reformed","owner":"mscdex","description":"A high-level form field handling and validation module for busboy","archived":false,"fork":false,"pushed_at":"2014-09-19T14:56:12.000Z","size":266,"stargazers_count":18,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-25T02:43:32.217Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mscdex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-04-11T20:55:38.000Z","updated_at":"2024-04-07T20:49:10.000Z","dependencies_parsed_at":"2022-08-07T08:01:24.896Z","dependency_job_id":null,"html_url":"https://github.com/mscdex/reformed","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Freformed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Freformed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Freformed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Freformed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mscdex","download_url":"https://codeload.github.com/mscdex/reformed/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248345203,"owners_count":21088231,"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":[],"created_at":"2024-10-13T12:07:33.112Z","updated_at":"2025-04-11T04:33:36.308Z","avatar_url":"https://github.com/mscdex.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Description\n===========\n\nA high-level form field handling and validation module for [busboy](https://github.com/mscdex/busboy).\n\n\nRequirements\n============\n\n* [node.js](http://nodejs.org/) -- v0.10.0+\n* [connect-busboy](https://github.com/mscdex/connect-busboy) (only useful when using this module with [Express](http://expressjs.com))\n* [busboy](https://github.com/mscdex/busboy) (only needed if not using `connect-busboy`)\n\n\nInstall\n============\n\n    npm install reformed\n\n\nExamples\n========\n\n* Parse and validate a form with Express and connect-busboy:\n\n```javascript\nvar express = require('express'),\n    app = express(),\n    busboy = require('connect-busboy'),\n    form = require('reformed');\n\n// ...\n\n// you should probably do better email validation than this in production\nfunction isValidEmail(key, val) {\n  return val.length \u003e 0 \u0026\u0026 val.length \u003c 255 \u0026\u0026 val.indexOf('@') \u003e -1;\n}\n\n// add a callback parameter for async validation\nfunction usernameUnused(key, val, cb) {\n  // db is some SQL database connection ...\n  db.query('SELECT id FROM users WHERE username = ? LIMIT 1'\n           [ val ],\n           function(err, rows) {\n    if (err)\n      return cb(err);\n    cb(null, !rows || !rows.length);\n  });\n}\n\n// ...\n\napp.post('/signup',\n         busboy({\n           limits: {\n             fields: 10, // max 10 non-multipart fields\n             parts: 10, // max 10 multipart fields\n             fileSize: 8 * 1000 * 1000 // files can be at most 8MB each\n           }\n         }),\n         form({\n           firstName: {\n             rules: [\n               { test: /^.{0,50}$/,\n                 error: 'First name must be 50 characters or less' }\n             ]\n           },\n           lastName: {\n             rules: [\n               { test: /^.{0,50}$/,\n                 error: 'Last name must be 50 characters or less' }\n             ]\n           },\n           emailAddress: {\n             required: true,\n             rules: [\n               { test: isValidEmail,\n                 error: 'Invalid email address' }\n             ]\n           },\n           avatar: {\n             filename: true, // use temporary file\n             maxSize: {\n               size: 1 * 1024 * 1024, // 1MB\n               error: 'Avatar file size too large (must be 1MB or less)'\n             }\n           },\n           username: {\n             required: true,\n             rules: [\n              { test: /^\\w{6,20}$/,\n                error: 'Username length must be between 6 and 20 alphanumeric characters' },\n              { test: usernameUnused,\n                error: 'Username already in use' }\n             ]\n           },\n           password: {\n             required: true,\n             rules: [\n               { test: /^.{6,}$/,\n                 error: 'Password must be at least 6 characters' }\n             ]\n           }\n         }),\n         function(err, req, res, next) {\n           if (!err || (err \u0026\u0026 err.key))\n             next(); // no error or validation-related error\n           else\n             next(err); // parser or other critical error\n         },\n         function(req, res, next) {\n           if (req.form.error) {\n             return res.send(400, 'Form error for field \"'\n                                  + req.form.error.key\n                                  + '\": '\n                                  + req.form.error);\n           }\n\n           // use `req.form.data` here ...\n\n           res.send(200, 'Thank you for your form submission!');\n         }\n);\n\napp.listen(8000);\n```\n\n* Parse and validate a form manually (without Express):\n\n```javascript\nvar http = require('http'),\n    Busboy = require('busboy'),\n    Form = require('reformed').Form;\n\n// ...\n\n// you should probably do better email validation than this in production\nfunction isValidEmail(key, val) {\n  return val.length \u003e 0 \u0026\u0026 val.length \u003c 255 \u0026\u0026 val.indexOf('@') \u003e -1;\n}\n\n// add a callback parameter for async validation\nfunction usernameUnused(key, val, cb) {\n  // db is some SQL database connection ...\n  db.query('SELECT id FROM users WHERE username = ? LIMIT 1'\n           [ val ],\n           function(err, rows) {\n    if (err)\n      return cb(err);\n    cb(null, !rows || !rows.length);\n  });\n}\n\n// ...\n\nvar signupFormCfg = {\n  firstName: {\n    rules: [\n      { test: /^.{0,50}$/,\n        error: 'First name must be 50 characters or less' }\n    ]\n  },\n  lastName: {\n    rules: [\n      { test: /^.{0,50}$/,\n        error: 'Last name must be 50 characters or less' }\n    ]\n  },\n  emailAddress: {\n    required: true,\n    rules: [\n      { test: isValidEmail,\n        error: 'Invalid email address' }\n    ]\n  },\n  avatar: {\n    filename: true, // use temporary file\n    maxSize: {\n      size: 1 * 1024 * 1024, // 1MB\n      error: 'Avatar file size too large (must be 1MB or less)'\n    }\n  },\n  username: {\n    required: true,\n    rules: [\n     { test: /^\\w{6,20}$/,\n       error: 'Username length must be between 6 and 20 alphanumeric characters' },\n     { test: usernameUnused,\n       error: 'Username already in use' }\n    ]\n  },\n  password: {\n    required: true,\n    rules: [\n      { test: /^.{6,}$/,\n        error: 'Password must be at least 6 characters' }\n    ]\n  }\n};\n\nhttp.createServer(function(req, res) {\n  if (req.method === 'POST' \u0026\u0026 req.url === '/signup') {\n    try {\n      var bb = new Busboy({\n            headers: req.headers,\n            limits: {\n              fields: 10, // max 10 non-multipart fields\n              parts: 10, // max 10 multipart fields\n              fileSize: 8 * 1000 * 1000 // files can be at most 8MB each\n            }\n          }),\n          form = new Form(signupFormCfg);\n\n      form.parse(bb, function(err) {\n        if (err) {\n          res.writeHead(err.key === undefined ? 500 : 400);\n          res.end(''+err);\n          return;\n        }\n\n        // use `form.data` here ...\n\n        res.writeHead(200);\n        res.end('Thank you for your form submission!');\n      });\n      req.pipe(bb);\n    } catch (err) {\n      res.writeHead(400);\n      res.end();\n    }\n    return;\n  }\n  res.writeHead(404);\n  res.end();\n}).listen(8000);\n```\n\n\nAPI\n===\n\n`require('reformed')` returns connect/express middleware.\n`require('reformed').Form` returns the **_Form_** class.\n\n\nForm methods\n------------\n\n* **(constructor)**(\u003c _object_ \u003efieldDefs[, \u003c _object_ \u003eoptions]) - Creates and returns a new Form instance. `fieldDefs` contains the form field definitions to be used for filtering and/or validation. `fieldDefs` is keyed on the field name and the value is an object that can contain any of the following properties:\n\n    *  **dataType** - \u003c _function_ \u003e - A function that takes in a non-file field value (string) or a buffered file field value (Buffer) and returns some other value. The default is to leave the value as-is.\n\n    *  **required** - \u003c _boolean_ \u003e - Indicates whether the field is required to be present in order for validation to pass. The default is `false`.\n\n    *  **multiple** - \u003c _boolean_ \u003e - Allow multiple instances of the same field. If `true`, this will set the field data value to an array of values. If `false`, the first value is kept and subsequent values are ignored. The default is `false`.\n\n    * **strictNotMultiple** - \u003c _boolean_ \u003e - Set to `true` to raise an error when multiple values are detected and `multiple: true` is not set. Otherwise silently ignore additional values.\n\n    *  **encoding** - \u003c _string_ \u003e - For buffered files, this is the Buffer encoding to use to convert the Buffer to a string. If `dataType` is present, that takes precedence over this setting. The default behavior is to leave the value as a Buffer.\n\n    * **filename** - \u003c _mixed_ \u003e - Set to a string to be used as the path to save this file to. Otherwise set to `true` to save the file to a temporary file. Streamed files will have a value of `{ filename: filepath, size: filesize }`, where `filepath` is the path to the saved file and `filesize` is the file's total size.\n\n    * **buffered** - \u003c _boolean_ \u003e - For files, set to `true` to buffer the entire contents of the file instead of writing to disk. Buffered files will have a value of `{ data: filebuf, size: filesize }`, where `filebuf` is a Buffer (unless `dataType` is used to convert the value) containing the data and `filesize` indicating the total number of bytes in the file.\n\n    * **stream** - \u003c _function_ \u003e - For files, set to a callback that accepts (and consumes) the Readable stream for this file. Streamed files will have a value of `{ size: filesize }`, where `filesize` indicates the total number of bytes streamed. RegExp-based rules are ignored in this case. **Note:** Remember that if a form upload results in failure (due to validation rules or otherwise), the place where you streamed the file to will still be there (if that matters for your application), so you may want to delete it in case of form upload failure.\n\n    *  **maxSize** - \u003c _mixed_ \u003e - Set to a number to restrict the max file size and use the default error message. Set to an object with `size` as the max file size and `error` as a custom string error message. If a max file size is set, it only takes effect if it is smaller than any configured busboy (global) max file size limit. The default is no limit (aside from any configured busboy limit).\n\n    *  **rules** - \u003c _array_ \u003e - A list of rules to apply for validation for this field. The default is to apply no rules. Each rule has the following fields:\n\n        *  **test** - \u003c _mixed_ \u003e - Set to a regular expression or a function. Notes:\n\n            * If a regular expression test is used for any file fields, the contents of the files are first converted to binary strings before testing the regular expression.\n\n            * Functions are called **synchronously** if the function has (2) `(key, val)` parameters for non-file fields or (3) `(key, filename, filesize)` parameters for file fields. These synchronous functions must return a boolean to indicate passage of the test, an _Error_ instance to use instead of the defined `error` (no `key` property will be set -- useful to indicate critical/system errors), or a string as a direct replacement for `error` (`key` property will be set). For streamed fields, the `filename` field will be set to `undefined`.\n\n            * Functions are called **asynchronously** if the function has (3) `(key, val, callback)` parameters for non-file fields or (4) `(key, filename, filesize, callback)` parameters for file fields. The callback has the parameters `(err, passedTest)`. For `err`, an _Error_ instance to use instead of the defined `error` (no `key` property will be set -- useful to indicate critical/system errors), or a string as a direct replacement for `error` (`key` property will be set). `passedTest` is a truthy value that should indicate if validation passed. For streamed fields, the `filename` field will be set to `undefined`.\n\n            * All functions are called with `this` set to the current entire data storage object. This can be handy for example if you need to reference other field values or want to enforce a max number of values for a field with `multiple: true` set.\n\n        *  **error** - \u003c _string_ \u003e - The (default) error message to use when the test fails.\n\n  `options` is an optional object with the following valid properties:\n\n    * **tmpdir** - \u003c _string_ \u003e - A path to be used for storing temporary files for file fields that do not have a specific filename set. If this is not provided, `os.tmpdir()` will be used.\n\n* **parse**(\u003c _Busboy_ \u003ebb, \u003c _function_ \u003ecallback) - _(void)_ - Starts reading form fields from the Busboy instance `bb`. `callback` is passed an _Error_ object on error. If a field didn't pass validation, the error object passed to the callback will have a `key` property set to the field name that failed validation. In case there were no errors, any/all form data is available on `form.data`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscdex%2Freformed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmscdex%2Freformed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscdex%2Freformed/lists"}