cofi

cofi

  • Docs
  • Form Demos
  • Components Demos
  • Layout Demos
  • Editor
  • GitHub

›Advanced Usages

Getting started

  • Introduction

Form Class

  • Installation
  • Overview
  • Arguments
  • Id
  • Data
  • Fields
  • Path
  • Component
  • Formatter & Parser
  • Dependencies
  • Required
  • Validators
  • Exclude Term
  • Disable Term
  • Require Term
  • Dependencies Change
  • Context
  • Dirty
  • Invalid
  • Errors
  • Processing
  • Pending Actions
  • Hooks
  • Term
  • Log
  • Error Codes
  • Actions
  • Definition Shorthand

Advanced Usages

  • Grid Usage
  • Form Persistency
  • Undo & Redo
  • Replay Actions
  • Track Actions
  • Server Validation
  • Entity Templates
  • Folder Structure

React Form

  • Installation
  • Overview
  • Form
  • Field
  • Components
  • Layout
  • Demos

Tools

  • Editor
  • DevTools

More Info

  • Test Cofi
  • Packages
  • Why Cofi?
  • Technologies
  • Videos
  • Channels

Contribute

  • Code of Conduct
  • Contributing Guide
  • Contributors

Server Validation

Writing form validations 2 times - both in the client and server can be a huge headache - consuming duplicate type of work and time, usually write them in a different language, and the validations might not be consistence in both sides.

Cofi allow you to do it only one time in a single place! Using a server Node.js you can expose an endpoint that validates the same form definition that was already defined to the client pages usage.

Cofi separates the logical form from the ui Form component. The logical form is a pure JavaScript form class that can be used both from the Form component (for example @cofi/react-form/Form component uses it behind the scene) as well as in a node server.

By defining a form definition schema in a common package - and use in it both in the client page (using Form component that gets that model & resources form definition as a props) as well as in a Node.js server - you reduce work duplication and time, consist all form related in one language support, and keep the validations synced in both client and server.

Architecture

server-validation

In the Node.js server - all you need to do to preform server validation is:

  1. Expose a POST endpoint for validation - that gets the form's data from the body and the form name as a url parameter
  2. Require the JavaScript Form class from @cofi/form
  3. Require the specific form definition using the formName param from the forms definitions common package
  4. Apply the data to the form definition model
  5. Initialize the Form class with the model & resources, and await for init to finish
  6. Check if the form is valid
  7. Return response valid / invalid

Example - Cofi Node Server

The following example demonstrate a simple Node.js server that validates a specific form with the given data from the client. Running node server.js will start the server and wait for requests.

Note: If a form used context object to contain dependent app data such as logged-in user data or permissions, you should override it in the server with the expected data (e.g get the logged-in user data from the server session for example)

endpoints/validate.js

const clone = require('lodash/clone');
const Form = require('@cofi/form').default;
const handleError = require('../handle-response').handleError;
const handleSuccess = require('../handle-response').handleSuccess;

const errors = {
  FORM_NAME_PARAM_MISSING:
  'formName was not defined as a url parameter. For example http://localhost:3001/validate?formName=user',
};
/*
 POST
 headers = { 'Content-Type': 'application/json' }
 url = http://localhost:3001/validate?formName=user
 body = { firstName: 'Monica' }
*/
module.exports.validate = async (req, res) => {  
  // get the form param. example: form=user
  let formName = req.url.query.formName;

  if (!formName) {
    return handleError(res, 500, errors.FORM_NAME_PARAM_MISSING);
  }
  
  // get form definition using dynamic require
  let { model, resources } = require(`../forms/${formName}`).default;
  model = clone(model);

  // add data from request body
  model.data = req.body || {};

  // init js-form and wait for init to finish
  const currForm = new Form();
  try {
    await currForm.init(model, resources);
  } catch (e) {
    return handleError(res, 500, e.message);
  }
  
  // check if form is valid
  const invalid = currForm.invalid;
  const result = invalid ? 'false' : 'true';

  // return response
  handleSuccess(res, result);
};

handle-response.js

module.exports.handleSuccess = (res, result) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end(result);
};

module.exports.handleError = (res, number, message) => {
  res.statusCode = number;
  res.setHeader('Content-Type', 'text/plain');
  res.end(`Cofi server error: ${message}`);
};

server.js

const http = require('http');
const url = require('url');
const handleError = require('./handle-response').handleError;
const validate = require('./endpoints/validate').validate;
const hostname = '127.0.0.1';
const port = 3001;

const supportedMethods = {
  POST: 'POST',
};

const router = {
  '/validate': validate,
};

const handleRequest = (request, response) => {
  const urlData = url.parse(request.url, true);
  const handler = router[urlData.pathname];

  if (!handler) {
    return handleError(response, 404, 'Page not found');
  }
 
  switch (request.method) {
    case supportedMethods.POST: {
      return handlePost(request, response, handler);
    }
    default: {
      return handleError(response, 405, `Method '${request.method}' not allowed`);
    }
  }
};

const handlePost = (request, response, handler) => {
  collectRequestData(request, body => {
    const req = { 
      url: url.parse(request.url, true),
      body,
    };
    handler(req, response);
  });
};

const collectRequestData = (request, callback) => {
  let body = '';
  request.on('data', chunk => {
    body += chunk.toString();

    // if POST data too big, kill the connection.
    // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
    if (body.length > 1e6) {request.connection.destroy();}
  });
  request.on('end', () => {
    callback(JSON.parse(body));
  });
};

const onServerStart = () => {
  console.log(`Cofi server is running at http://${hostname}:${port}`); // eslint-disable-line
};

// create and start server
const server = http.createServer(handleRequest);

server.listen(port, hostname, onServerStart);

forms/user

const inputText = require('@cofi/react-components/edit/Text').default;

const model = {
  fields = {
    firstName: {
      label: 'First Name',
      description: 'Enter user first name',
      path: 'firstName',
      component': {
        name: 'inputText'
      },
      validators: [
        {
          name: 'uniqueName'
        }
      ]
    }
  },
};

const resources = {
  components: {
    inputText: { renderer: inputText },
  },
  validators = {
    uniqueName: {
      func: props => new Promise((resolve) => {
        setTimeout(() => {
          const allServerNames = ['Rachel', 'Monica'];
          const isNameUnique = !allServerNames.includes(props.value || '');
          resolve(isNameUnique);
        }, 1);
      }),
      message: props => `${props.label} should be unique`,
    },
  },
};

module.exports.default = {
  model,
  resources,
};

Example - Client request

  • Method = POST
  • Headers = { 'Content-Type': 'application/json' }
  • Url = http://localhost:3001/validate?formName=user
  • Body =
    1. { firstName: 'Monica' } - Will return invalid - since 'Monica' is already taken and firstName has unique name validation.
    2. { firstName: 'Ross' } - Will return valid.
← Track ActionsEntity Templates →
  • Architecture
  • Example - Cofi Node Server
    • endpoints/validate.js
    • handle-response.js
    • server.js
    • forms/user
  • Example - Client request
cofi
Copyright © 2021 Verizon Media
DOCS
IntroductionForm ClassReact FormReact ComponentsReact Layout
DEMOS
React FormReact ComponentsReact Layout
CONTRIBUTING
Code Of ConductContributing GuideContributorsUsers
CHANNELS
StarGitHubStack OverflowMediumVideos