Registration forms, checkout, comments, and profile settings are gateways for data into web applications. The problem is, data that appears correct in the browser may not be safe or compliant when it reaches the server.
This is why validation should not be solely placed in JavaScript. The browser helps users fill out forms conveniently, but the server and database must still serve as the final layer of checks. If any layer is bypassed, corrupted data can spread to reports, notifications, payment processes, and even other systems that use that data.
Browser validation is not the main guard
Client-side validation runs in the user's browser. For example, JavaScript checks whether an email address has the correct format or whether the name field cannot be empty.
This layer is useful because it provides quick feedback. Users do not need to submit the form to the server just to find out that the phone number has not been filled in. However, all rules in the browser can be bypassed. Users can disable JavaScript, modify requests through DevTools, or send data directly using curl and Postman.
Therefore, browser validation should be considered as a user experience aid, not as a security mechanism.
const email = document.querySelector('#email').value.trim();
if (!email.includes('@')) {
showError('Please enter a valid email address');
}Code like this makes the form feel friendlier, but it does not guarantee that the server only accepts valid email addresses.
The server must recheck all important rules
The server receives requests from various sources, not just from forms created by your team. Therefore, checks on the server must stand alone and not assume that the browser has done its job.
For example, an endpoint receives user registration data. The server needs to check at least:
- Required fields are actually present.
- Data types are as expected.
- Text length is within reasonable limits.
- Values are within the allowed options.
- Email is not already in use if it must be unique.
- Numbers are not negative if the context does not allow it.
A simple example in PHP:
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);
$errors = [];
if ($name === '') {
$errors['name'] = 'Name is required';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Invalid email';
}
if ($age === false || $age < 18) {
$errors['age'] = 'Minimum age is 18';
}This example is not a complete protection for all applications, but it illustrates an important principle: the server reads, cleans, and checks data before processing it.
Differentiate between "valid" data and "reasonable" data
Data can pass format checks but still be unreasonable from a business perspective. A value of quantity=3 may be valid as a number, but it may not be purchasable if only one item is left in stock.
Therefore, validation typically has two layers:
- Form validation: checks the type, format, length, and structure of the data.
- Business rule validation: checks whether the data is allowed under the current application conditions.
For example, in an ordering system, the server needs to check the price and stock from a trusted source. Do not accept prices from the browser just because they appear correct. Prices submitted by users can be altered before the request is sent.
The server should accept the product ID and quantity, then fetch the latest price from the database. This way, important values are not determined by input that is outside the application's control.
Databases also need to be fenced
Application validation is crucial, but databases should not be left unprotected. Database constraints can prevent certain data from entering even if there are bugs in the application code.
Some common fences include:
NOT NULLfor columns that must have a value.UNIQUEfor emails, usernames, or codes that must not be the same.CHECKto limit certain values, if supported by the database system.- Foreign key to maintain consistency between tables.
For example, user emails must be unique. Checks in PHP are still necessary to ensure error messages can be generated properly, but the UNIQUE constraint in the database serves as the last line of defense when two requests come in almost simultaneously.
Use prepared statements, not building SQL from input
Once data is validated, how it is inserted into the database also determines the security of the application. Avoid constructing SQL queries by concatenating strings from user input.
Use prepared statements so that input values are treated as data, not as part of the SQL command.
$stmt = $pdo->prepare(
'INSERT INTO users (name, email) VALUES (:name, :email)'
);
$stmt->execute([
':name' => $name,
':email' => $email
]);Prepared statements do not replace validation. Both serve different purposes: validation ensures data complies with rules, while prepared statements help separate data from the query structure.
Don't forget normalization and output encoding
The stored data also needs to be considered in its form. Emails can usually be normalized by trimming whitespace at the beginning and end. However, do not indiscriminately change all input because some data is indeed sensitive to case or specific formats.
Moreover, data that is safe to store is not necessarily safe to display. User names or comments may contain special characters that can be dangerous if directly inserted into HTML. When displaying data, use output escaping according to its context, such as HTML, attributes, JavaScript, or URLs.
The simple principle is: clean as needed, validate according to rules, and escape when data is output to the view.
How to make the flow neater?
In small projects, validation is often written directly in the controller. This method is quick, but it becomes cumbersome when the same rules are used by web forms, APIs, and data import processes.
Consider separating the following parts:
- Request parsing: extracting data from the request.
- Validation: checking format and basic rules.
- Business rule: checking application and database conditions.
- Persistence: storing data using safe queries.
- Response: returning messages that are understandable to users.
This separation makes the code easier to test. You can test validation rules without always having to send requests through the browser.
What can be done now
Audit one important form in your application, such as the registration or checkout form. Ensure the following rules are available on the server:
- Empty input and input with incorrect types are rejected.
- Input length has limits.
- Important values are not trusted from the browser.
- Database queries use prepared statements.
- Important columns have appropriate constraints.
- Error messages do not leak internal database details.
- Data is escaped when displayed back.
Good validation does not mean putting users through many obstacles. The goal is to ensure data remains correct without sacrificing the user experience. The browser can assist users, the server can enforce rules, and the database can serve as the final fence. All three work together to prevent small mistakes from becoming major issues throughout the application.
โ Rio Yotto @rioyotto
