Forms, File Uploads and Security with Node.js and Express

3D ad banner ad revenue ad revenues adobe XD Advanced advanced-css-tools advanced-css-tutorials AdvancedCSS AdvancedCSS-hub agency Agile Agile Development ai Ajax Alex Skill Kit Alexa Alexa skill AlexW Amazon Alexa amazon developer analytics Angelap angular AngularJS Animation anime.js ANN api apollo Appsflyer array methods artificial neural network Async async components AurelioD automation Avocode AWS AWS Lambda axios b2b b2c baas background background eraser tool Banner banner ad Bannersnack bawmedia bem bitcoin black friday Blogs bootstrap bot browser testing Browsers build process bundler Business business success C Canvas & SVG career Career Advancement CDN Chakra UI charting chat app chatbots ChatKit chrome chrome developer tools chrome extension chrome extensions chromium ciphering cli cli tools climate climate action clojure Cloud cloud hosting CloudFlare Cloudways cms CNN code challenge ColinI collaboration collaboration tools committing changes compiler components const assertions constructs content content delivery network context api convoluted neural network create react app cron job cross browser testing cross platform development cross-browser cryptocurrencies cryptocurrency CSS CSS Animations CSS Architecture css blur css challenge css framework css frameworks css grid css modules CSS print css rem css typography CSS-in-JS CSS3 css3 transforms cssbattle.dev D3.js data science data visualization database databases debugging decoupled cms deep linking delay Design Design & UX design career design handoff Detox dev environment developer tools DevOps devtools diagnostic analytics digit recognition digital agency digitalocean Discord discounts django Docker Docker Containers E-commerce ECMAScript edge computing editor elastic em units employment encryption end-to-end testing energy energy usage Entrepreneur Entrepreneurship environment erasing backgrounds es6 ES6 classes eslint Expo Express FaaS face detection face recognition fast wordpress themes Feathers Feathers.js figma flask flatfile flexible wordpress theme forms tutorial framework Frameworks freelancing functional programming functions gatsby generators geolocation getting a job gitCS github github API Google Photos GraphCMS graphing graphql Growth Grunt grunt-watch Gulp haskell headless CMS hooks hosting HTML HTML & CSS html preprocessor HTML5 HTML5 Dev Center https://www.sitepoint.com/windows-linux-subsystem-2-windows-terminal IAAS IDE Illustration image optimization immutability Interview ionic IP2Location ipdata jade jamesh JavaScript javascript framework javascript frameworks javascript tooling Jest job job interview joelf jQuery jQuery functions jquery get url params jquery string functions jQuery.each() jQuery.each() Array Example jquery4u js js framework JS tooling Keras keycdn kubernetes lambdatest launch checklist Layout learn javascript learn typescript learn-advanced-css learn-modernjs Learn-Node-JS learn-vue Let's Encrypt Lift linux linux containers location tracking LouisL machine learning Managed managed hosting mariap marvel MATLAB MBaaS mekanism mind map mind mapping mindmapping Mobile mobile attribution Mobile Design modernjs modernjs-hub modernjs-tools modernjs-tutorials modules monday mongodb MTCNN multi-threading mysql MySQL install natural language processing Navigation navigation library nest Next.js nilsonj node node version manager node-hub Node-JS-Tools Node-JS-Tutorials node-mysql module Node.js nodejs noops npm nvm objects octobercms Offers on-premise online publishing PaaS Package Management package manager pair programming pairing pause Percy Performance Photography & Imagery Photoshop Photoshop Tutorials & Articles pip pipeline portfolio portfolio builders preprocessors prettier print styles Prisma Private Class Fields product design Profitability Programming programming job PubNub Pug Puppeteer Pusher python python framework python interview python job Quasar query strings quick-tip quiz quote builders r Raw JavaScript React react components React Hooks React Hooks Form React native reactive Reactive Programming reactive web apps read offline Reat native elements Recurring Payments recursive types RedBox redux rem units remote work remote work tips removing backgrounds resilient Resources responsive REST restful api Review SaaS sampling sass Scss self sencha server server-side serverless serverless computing shared hosting sharing state sharpen side project siteground sketch slashdata sleep Software software commits sorting specifications speech recognition speech recognition api sponsored ssl Stack Overflow state state management static module bundler StudioWorks Styled Components styling React components survey sustainability svelte sympli Tailwind task runner telegram bots Template engine Testing text to speech Themes Tic Tac Toe Tinder toggle Tolerance tooling tooling anxiety tools Tools & Libraries transformations TypeScript typescript skills Ubuntu UI UI Design Usability user experience user research UX ux issues vanilla javascript VGGFace2 virtual machine visual testing voice recognition vps vue Vue CLI vue dev environment vue-hub vue-tutorials vue.js vuetify vuex wait WatermelonDB Web Web Design web hosting Web Hosting & Domains Web standards web workers webpack website builders website performance WebSockets windows windows 10 home Windows Subsystem for Linux Windows Terminal wix women women in tech WordPress wordpress plugin wordpress themes WordPress theming work workers workflow working remotely workplaces WPEngine WSL WSL2 YellowBox Zeplin

Forms, File Uploads and Security with Node.js and Express

If you’re building a web application, you’re likely to encounter the need to build HTML forms on day one. They’re a big part of the web experience, and they can be complicated.

Typically the form-handling process involves:

  • displaying an empty HTML form in response to an initial GET request
  • user submitting the form with data in a POST request
  • validation on both the client and the server
  • re-displaying the form populated with escaped data and error messages if invalid
  • doing something with the sanitized data on the server if it’s all valid
  • redirecting the user or showing a success message after data is processed.

Handling form data also comes with extra security considerations.

We’ll go through all of these and explain how to build them with Node.js and Express — the most popular web framework for Node. First, we’ll build a simple contact form where people can send a message and email address securely and then take a look what’s involved in processing file uploads.

A contact form with email and message with validation errors

As ever, the complete code can be found in our GitHub repo.

Setup

Make sure you’ve got a recent version of Node.js installed. node -v should return 8.9.0 or higher.

Download the starter code from here with Git:

git clone -b starter https://github.com/sitepoint-editors/node-forms.git node-forms-starter
cd node-forms-starter
npm install
npm start

Note: The repo has two branches, starter and master. The starter branch contains the minimum setup you need to follow this article. The master branch contains a full, working demo (link above).

There’s not too much code in there. It’s just a bare-bones Express setup with EJS templates and error handlers:

// server.js
const path = require('path');
const express = require('express');
const layout = require('express-layout');

const routes = require('./routes');
const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

const middlewares = [
  layout(),
  express.static(path.join(__dirname, 'public')),
];
app.use(middlewares);

app.use('/', routes);

app.use((req, res, next) => {
  res.status(404).send("Sorry can't find that!");
});

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

app.listen(3000, () => {
  console.log('App running at http://localhost:3000');
});

The root url / simply renders the index.ejs view:

// routes.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.render('index');
});

module.exports = router;

Displaying the Form

When people make a GET request to /contact, we want to render a new view contact.ejs:

// routes.js
router.get('/contact', (req, res) => {
  res.render('contact');
});

The contact form will let them send us a message and their email address:

<!-- views/contact.ejs -->
<div class="form-header">
  <h2>Send us a message</h2>
</div>
<form method="post" action="/contact" novalidate>
  <div class="form-field">
    <label for="message">Message</label>
    <textarea class="input" id="message" name="message" rows="4" autofocus></textarea>
  </div>
  <div class="form-field">
    <label for="email">Email</label>
    <input class="input" id="email" name="email" type="email" value="" />
  </div>
  <div class="form-actions">
    <button class="btn" type="submit">Send</button>
  </div>
</form>

See what it looks like at http://localhost:3000/contact.

Form Submission

To receive POST values in Express, you first need to include the body-parser middleware, which exposes submitted form values on req.body in your route handlers. Add it to the end of the middlewares array:

// server.js
const bodyParser = require('body-parser');

const middlewares = [
  // ...
  bodyParser.urlencoded({ extended: true }),
];

It’s a common convention for forms to POST data back to the same URL as was used in the initial GET request. Let’s do that here and handle POST /contact to process the user input.

Let’s look at the invalid submission first. If invalid, we need to pass back the submitted values to the view (so users don’t need to re-enter them) along with any error messages we want to display:

router.get('/contact', (req, res) => {
  res.render('contact', {
    data: {},
    errors: {}
  });
});

router.post('/contact', (req, res) => {
  res.render('contact', {
    data: req.body, // { message, email }
    errors: {
      message: {
        msg: 'A message is required'
      },
      email: {
        msg: 'That email doesn‘t look right'
      }
    }
  });
});

If there are any validation errors, we’ll do the following:

  • display the errors at the top of the form
  • set the input values to what was submitted to the server
  • display inline errors below the inputs
  • add a form-field-invalid class to the fields with errors.
<!-- views/contact.ejs -->
<div class="form-header">
  <% if (Object.keys(errors).length === 0) { %>
    <h2>Send us a message</h2>
  <% } else { %>
    <h2 class="errors-heading">Oops, please correct the following:</h2>
    <ul class="errors-list">
      <% Object.values(errors).forEach(error => { %>
        <li><%= error.msg %></li>
      <% }) %>
    </ul>
  <% } %>
</div>

<form method="post" action="/contact" novalidate>
  <div class="form-field <%= errors.message ? 'form-field-invalid' : '' %>">
    <label for="message">Message</label>
    <textarea class="input" id="message" name="message" rows="4" autofocus><%= data.message %></textarea>
    <% if (errors.message) { %>
      <div class="error"><%= errors.message.msg %></div>
    <% } %>
  </div>
  <div class="form-field <%= errors.email ? 'form-field-invalid' : '' %>">
    <label for="email">Email</label>
    <input class="input" id="email" name="email" type="email" value="<%= data.email %>" />
    <% if (errors.email) { %>
      <div class="error"><%= errors.email.msg %></div>
    <% } %>
  </div>
  <div class="form-actions">
    <button class="btn" type="submit">Send</button>
  </div>
</form>

Submit the form at http://localhost:3000/contact to see this in action. That’s everything we need on the view side.

Validation and Sanitization

There’s a handy middleware called express-validator for validating and sanitizing data using the validator.js library. Let’s add it to our app.

Validation

With the validators provided, we can easily check that a message and a valid email address was provided:

// routes.js
const { check, validationResult, matchedData } = require('express-validator');

router.post('/contact', [
  check('message')
    .isLength({ min: 1 })
    .withMessage('Message is required'),
  check('email')
    .isEmail()
    .withMessage('That email doesn‘t look right')
], (req, res) => {
  const errors = validationResult(req);
  res.render('contact', {
    data: req.body,
    errors: errors.mapped()
  });
});

Sanitization

With the sanitizers provided, we can trim whitespace from the start and end of the values, and normalize the email address into a consistent pattern. This can help remove duplicate contacts being created by slightly different inputs. For example, ' Mark@gmail.com' and 'mark@gmail.com ' would both be sanitized into 'mark@gmail.com'.

Sanitizers can simply be chained onto the end of the validators:

// routes.js
router.post('/contact', [
  check('message')
    .isLength({ min: 1 })
    .withMessage('Message is required')
    .trim(),
  check('email')
    .isEmail()
    .withMessage('That email doesn‘t look right')
    .bail()
    .trim()
    .normalizeEmail()
], (req, res) => {
  const errors = validationResult(req);
  res.render('contact', {
    data: req.body,
    errors: errors.mapped()
  });

  const data = matchedData(req);
  console.log('Sanitized:', data);
});

The matchedData function returns the output of the sanitizers on our input.

Also, notice our use of the bail method, which stops running validations if any of the previous ones have failed. We need this because if a user submits the form without entering a value into the email field, the normalizeEmail will attempt to normalize an empty string and convert it to an @. This will then be inserted into our email field when we re-render the form.

The post Forms, File Uploads and Security with Node.js and Express appeared first on SitePoint.

Original Article

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.