Skip to content

Input validation in TypeScript

Published: at 10:00 AM (8 min read)

Introduction

Users can be unpredictable. They can enter anything they want in your application, and you need to be prepared for it. One way to protect your application from security vulnerabilities is to validate the input from the user. In this article, I will show you how to validate user input in TypeScript.

Table of contents

Open Table of contents

What you need to protect against

When you are building an application, you need to protect against different types of attacks. Here are some of the most common attacks you need to protect against:

To protect against these attacks, you need to validate the input from the user. You need to make sure that the input is what you expect it to be and that it does not contain any malicious code.

Frontend validation

You can validate the input from the user in different places in your application. Your first line of defense should be the client-side validation. You can use HTML5 input types and attributes to validate the input from the user. For example, you can use the required attribute to make sure that a field is not empty.

<input type="text" name="username" required />

This is a good start, but it’s not enough. You also need to validate the input on that the user sends to the server. You can do it in the frontend to prevent unnecessary requests to the server, but you should always validate the input on the server as well. You can use a library like Joi or Yup but my personal favorite is zod because it is simple, easy to use and has a great typescript integration. Zod does integrate well with react-hook-form. In this article, you will learn to build a simple login form like this one that check if the email is a valid email and the password is at least 10 characters long and contains at least one uppercase letter, one number and one special character.

Installation

You need to install the following packages to use zod and react-hook-form:

npm install react-hook-form @hookform/resolvers zod

Your default components should look like this:

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

function FormDemo() {
  return <div>FormDemo</div>;
}

export default FormDemo;

There is a lot of things we aren’t using yet, but we will get there. First, let’s create a schema for our form. We will use zod for this. You can validate the input to be an email by using the .email() method and the password to be at least 10 characters long with .min(10). We also want the password to contain at least one uppercase letter, one number and one special character. We can achieve this with the .regex() method. The regex method can also be used to check for SQL injection or other malicious code. There are plenty of regex patterns available online to check for these things.

const loginSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(10)
    .regex(/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s]).*$/),
});

function FormDemo() {
  return <div>FormDemo</div>;
}

export default FormDemo;

Now we can use the schema to validate the input from the user. We can use the useForm hook from react-hook-form to create a form with validation. We can pass the schema to the useForm hook with the resolver option. We can use the register function to register the input fields in the form. We can use the errors object to display the error messages to the user.

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(10)
    .regex(/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s]).*$/),
});

function FormDemo() {
  const { register } = useForm<z.infer<typeof loginSchema>>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  return (
    <form>
      <input {...register("email")} type="email" placeholder="Email" />
      <input {...register("password")} type="password" placeholder="Password" />
      <button type="submit">Submit</button>
    </form>
  );
}

export default FormDemo;

The last step is to handle the form submission. We can use the handleSubmit function from react-hook-form to handle the form submission. We can create a function that will be called when the form is submitted. We can use the onSubmit option to pass the function to the handleSubmit function. It’s also possible to display the error messages to the user by using the errors object.

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(10)
    .regex(/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s]).*$/),
});

function FormDemo() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<z.infer<typeof loginSchema>>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  function onSubmit(data: z.infer<typeof loginSchema>) {
    alert(data.email);
    alert(data.password);
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} type="email" placeholder="Email" />
      {errors.email && <p>{errors.email.message}</p>}
      <input {...register("password")} type="password" placeholder="Password" />
      {errors.password && <p>{errors.password.message}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

export default FormDemo;

You can add some styles to the form to make it look better. I used tailwind for this.

import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(10)
    .regex(/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s]).*$/),
});

function FormDemo() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<z.infer<typeof loginSchema>>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      email: "",
      password: "",
    },
  });

  function onSubmit(data: z.infer<typeof loginSchema>) {
    alert(data.email);
    alert(data.password);
  }

  return (
    <form className="w-full max-w-sm" onSubmit={handleSubmit(onSubmit)}>
      <div className="mb-6 md:flex md:items-center">
        <div className="md:w-1/3">
          <label
            className="mb-1 block pr-4 font-bold text-gray-500 md:mb-0 md:text-right"
            htmlFor="email"
          >
            Email
          </label>
        </div>
        <div className="md:w-2/3">
          <input
            id="email"
            className="w-full appearance-none rounded border-2 border-gray-200 bg-gray-200 px-4 py-2 leading-tight text-gray-700 focus:border-purple-500 focus:bg-white focus:outline-none"
            type="email"
            placeholder="John Doe"
            {...register("email")}
          />
        </div>
        {errors.email && (
          <span className="text-red-500">Email must be a valid email</span>
        )}
      </div>
      <div className="mb-6 md:flex md:items-center">
        <div className="md:w-1/3">
          <label
            className="mb-1 block pr-4 font-bold text-gray-500 md:mb-0 md:text-right"
            htmlFor="password"
          >
            Password
          </label>
        </div>
        <div className="md:w-2/3">
          <input
            id="password"
            className="w-full appearance-none rounded border-2 border-gray-200 bg-gray-200 px-4 py-2 leading-tight text-gray-700 focus:border-purple-500 focus:bg-white focus:outline-none"
            type="password"
            placeholder="******************"
            {...register("password")}
          />
        </div>
        {errors.password && (
          <span className="text-red-500">
            Password must contain at least 10 characters, 1 uppercase letter, 1
            number, and 1 special character
          </span>
        )}
      </div>
      <div className="md:flex md:items-center">
        <div className="md:w-1/3"></div>
        <div className="md:w-2/3">
          <button
            className="focus:shadow-outline rounded bg-purple-500 px-4 py-2 font-bold text-white shadow hover:bg-purple-400 focus:outline-none"
            type="submit"
          >
            Sign Up
          </button>
        </div>
      </div>
    </form>
  );
}

export default FormDemo;

And that’s it! You have created a simple login form with validation in TypeScript. You can use this form as a starting point for your own forms. You can add more fields to the form and more validation rules to the schema. You can also add more styles to the form to make it look better. I hope this article was helpful to you. If you have any questions or comments, feel free to leave them below. Thank you for reading! 🚀

Backend validation

A simple user will only use the form you provide, but a malicious user might not. He can bypass your frontend validation by forging the http request with tools like curl or postman. That’s why you should always validate the input on the server as well. You can use the same schema you used in the frontend to validate the input on the server. Some libraries like Trpc have built-in support for zod. You can use the zod method to validate the input from the user. But in this article, I will keep it simple and show you how to validate the input with express.

The setup of express is out of the scope of this article, but you can find good tutorials on the internet. I would recommend you to search for a tutorial using typescript. Once the setup is done, you can create a new route to handle the form submission. You can use the same schema you used in the frontend

import express from "express";
import { z } from "zod";

const loginSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(10)
    .regex(/^(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s]).*$/),
});

const app = express();

app.post("/login", (req, res) => {
  try {
    const data = loginSchema.parse(req.body);
    // Do something with the datas
    res.status(200).json({ message: "Success" });
  } catch (error) {
    res.status(400).json({ error: error.errors });
  }
});

And that’s it! You can simply parse the body of the request with the schema and send a response back to the user. You can also use the zod method to validate the query parameters or the headers of the request. I hope this article was helpful to you. You builded a simple login form with validation in TypeScript ! 🚀

Conclusion

I hope this article has given you some insights into how to validate user input in TypeScript. You have learned how to validate the input from the user in the frontend and in the backend. You have also learned how to use zod to create a schema for your form. I didn’t cover the SQLinjection and other malicious code in this article, but you can use the .regex() method to check for these things. Thank you for reading! 🚀

Support me : Buy Me A Coffee