Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
YouTube Security VideosAndroid Police: Samsung is smashing records! #shorts #tech #phones(21.09.2026 um 13:55 Uhr)
YouTube Security Videosheise & c't: Bundesnetzagentur wollte diesen Futterautomaten verbieten(21.09.2026 um 13:53 Uhr)
YouTube Security VideosNeil Patel: Your Google Traffic Isn't An Asset It's A Loan #shorts(21.09.2026 um 14:05 Uhr)
Windows Tipps & SecurityF-14 A Tomcat Top Gun endlich als Revell Klemmbausteinmodell erhältlich(21.09.2026 um 14:27 Uhr)
Sichere ProgrammierungShow the Hand-Back Sample Before Approving an Agent Score(21.09.2026 um 14:15 Uhr)
Sichere ProgrammierungHybrid retrieval in one Postgres query: RRF over tsvector + pgvector(21.09.2026 um 14:15 Uhr)
YouTube Security VideosAndroid Police: Samsung is smashing records! #shorts #tech #phones(21.09.2026 um 13:55 Uhr)
YouTube Security Videosheise & c't: Bundesnetzagentur wollte diesen Futterautomaten verbieten(21.09.2026 um 13:53 Uhr)
YouTube Security VideosNeil Patel: Your Google Traffic Isn't An Asset It's A Loan #shorts(21.09.2026 um 14:05 Uhr)
Windows Tipps & SecurityF-14 A Tomcat Top Gun endlich als Revell Klemmbausteinmodell erhältlich(21.09.2026 um 14:27 Uhr)
Sichere ProgrammierungShow the Hand-Back Sample Before Approving an Agent Score(21.09.2026 um 14:15 Uhr)
Sichere ProgrammierungHybrid retrieval in one Postgres query: RRF over tsvector + pgvector(21.09.2026 um 14:15 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Using the same validation logic on the client and server side with Isomorphic-validation.

In this post I want to briefly describe the usage of Isomorphic-validation, a javascript validation library that was intended to be used on the client and server side and make the process of validating user input seamless across your…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

In this post I want to briefly describe the usage of Isomorphic-validation, a javascript validation library that was intended to be used on the client and server side and make the process of validating user input seamless across your application.



In the two previous posts I covered some aspects of using this library on the client side by creating a sign-in and sign-up form:





Here I will show why this library is actually isomorphic. I'm going to incorporate that sign-in and sign-up examples into a simple Node.js application.



If you want to run this project locally it is there:



View on Github






0. Setting up the project



First, I created the following folder structure:




.
⊟── public
│ ⊞─── bundles
│ ├── signin.html
│ ├── signup.html
│ └── style.css
├── repository.js
⊟─── validation
⊞── profiles
⊞── ui
⊞── validations
⊞── validators







repository.js




// repository.js

/**
* Mock repository
*/


const existingEmails = new Map([
['[email protected]', 1],
['[email protected]', 2],
['[email protected]', 3],
['[email protected]', 4],
]);

export default {
async getUserIdBy({ email = '' }) {
return existingEmails.get(email);
},
};











public/signin.html




<!-- public/signin.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Sign in</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<form action="signin" method="post" name="signinForm" autocomplete="off">
<h2>Sign in</h2>

<label class="form-field" for="email">
<input type="text" name="email" id="email" required />
<span class="field-title">E-mail</span>
</label>

<label class="form-field" for="password">
<input type="password" name="password" id="password" required />
<span class="field-title">Password</span>
</label>

<input type="submit" name="submitBtn" disabled />
</form>
<script type="module" src="bundles/signin.js"></script>
</body>
</html>











public/signup.html




<!-- public/signup.html -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Sign up</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<form action="signup" method="post" name="signupForm" autocomplete="off">
<h2>Sign up</h2>

<label class="form-field" for="email">
<input type="text" name="email" id="email" required />
<span class="field-title">E-mail</span>
</label>

<label class="form-field" for="password">
<input type="password" name="password" id="password" required />
<span class="field-title">Password</span>
</label>

<label class="form-field" for="pwdConfirm">
<input type="password" name="pwdConfirm" id="pwdConfirm" required />
<span class="field-title">Password confirmation</span>
</label>

<input type="submit" name="submitBtn" disabled />
</form>
<script type="module" src="bundles/signup.js"></script>
</body>
</html>











public/style.css




// public/style.css

* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: Helvetica;
}

body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: lightslategray;
}

h2 {
text-align: center;
width: 100%;
}

form {
display: flex;
flex-wrap: wrap;
gap: 48px;
justify-content: flex-start;
align-items: center;
width: min-content;
padding-inline: 24px;
padding-block: 32px;
border: 1px solid slategray;
border-radius: 2px;

background: rgb(255,255,255);
background: linear-gradient(123deg, rgba(255,255,255,1) 24%, rgba(244,250,255,1) 51%, rgba(255,255,255,1) 77%);
}

label {
padding: 4px;
background-color: white;
}

input {
height: 32px;
width: 100%;
border: none;
background: none;
outline: none;
font-size: 16px;
}

input[type="submit"] {
height: 42px;
border: 1px solid lightslategray;
background-color: slategray;
color: white;
transition: opacity .1s;
}

input[type="submit"]:disabled {
opacity: .5;
cursor: not-allowed;
}

input[type="submit"]:not(:disabled):hover {
filter: brightness(1.1);
}

.form-field {
width: 220px;
position: relative;
border: 1px solid slategray;
border-radius: 2px;
cursor: text;
}

.field-title {
position: absolute;
top: 0;
padding-block: 4px;
display: grid;
align-items: center;
height: 100%;
color: slategray;
transition: font-size .1s, height .1s ease-out;
}

.form-field > input:valid + .field-title {
font-size: 12px;
height: 20px;
}

.form-field > input:valid {
padding-top: 14px;
}









I didn't want to set up the whole database for this simple demonstration, so I just created a mock repository file with registerd e-mail addresses in order to implement the checking an e-mail for existance functionality on the sign-up form.






1. Preparing validators






.
⊞─── public
├── repository.js
⊟─── validation
⊞── profiles
⊞── ui
⊞── validations
⊟── validators
├── is-email.js
├── is-email-not-registered.js
├── is-email-not-registered-s.js
├── is-max-length.js
├── is-min-length.js
├── is-password-confirmed.js
└── is-strong-password.js







validation/validators/is-email.js




// validation/validators/is-email.js

import { Predicate } from 'isomorphic-validation';
import isEmail from 'validator/es/lib/isEmail';

export default Predicate(isEmail, {
err: 'Must be in the E-mail format.',
});











validation/validators/is-email-not-registered.js




// validation/validators/is-email-not-registered.js

import { Predicate } from 'isomorphic-validation';

const isEmailNotRegistered = value =>
fetch('/checkemail', { method: 'post', body: new URLSearchParams({ email: value }) }).then(res => res.json());

export default Predicate(isEmailNotRegistered, {
err: 'The E-mail must not be already registered.',
waitMsg: 'Wait a moment, we are checking your E-mail.',
});











validation/validators/is-email-not-registered-s.js




// validation/validators/is-email-not-registered-s.js

import repository from '../../repository.js';

const isEmailNotRegisteredS = async value => !(await repository.getUserIdBy({ email: value }));

export default isEmailNotRegisteredS;











validation/validators/is-max-length.js




// validation/validators/is-max-length.js

import { Predicate } from 'isomorphic-validation';
import isLength from 'validator/es/lib/isLength';

const isMaxLength = max =>
Predicate(value => isLength(value, { max }), {
err: `Should not be longer than ${max} characters.`,
});

export default isMaxLength;











validation/validators/is-min-length.js




// validation/validators/is-min-length.js

import { Predicate } from 'isomorphic-validation';
import isLength from 'validator/es/lib/isLength';

const isMinLength = min =>
Predicate(value => isLength(value, { min }), {
err: `Must be at least ${min} characters long.`,
});

export default isMinLength;











validation/validators/is-password-confirmed.js




// validation/validators/is-password-confirmed.js

import { Predicate } from 'isomorphic-validation';
import equals from 'validator/es/lib/equals';

export default Predicate(equals, {
err: 'Password and password confirmation must be the same',
});











validation/validators/is-strong-password.js




// validation/validators/is-strong-password.js

import { Predicate } from 'isomorphic-validation';
import isStrongPassword from 'validator/es/lib/isStrongPassword';

export default Predicate(isStrongPassword, {
err: 'Min. 8 symbols, 1 capital letter, 1 number, 1 special character',
});










Here I'm using validators from the library validator.js. You can use validators provided by other libraries or write your own. They only have to return a Boolean value or a Promise that fullfils with a Boolean value. I wrap them in Predicate in order to pass error messages along with them. If your project requires internationalization you can pass translation keys instead. See an example of usage with i18next library.



There are two validators that will be checking an e-mail registration, one is-email-not-registered.js which will be executed on the client side and make a request to the server, and is-email-not-registered-s.js which will be making a request to the database on the server, that is in our case to the mock repository.






2. Preparing validations






.
⊞─── public
├── repository.js
⊟─── validation
⊞── profiles
⊞── ui
⊟── validations
│ ├── email.js
│ └── password.js
⊞─── validators







validation/validations/email.js




// validation/validations/email.js

import { Validation } from 'isomorphic-validation';
import isEmail from '../validators/is-email.js';
import isMinLength from '../validators/is-min-length.js';
import isMaxLength from '../validators/is-max-length.js';

const emailV = Validation()
.constraint(isMinLength(8), { next: false })
.constraint(isMaxLength(48), { next: false })
.constraint(isEmail, { next: false });

export default emailV;











validation/validations/password.js




// validation/validations/password.js

import { Validation } from 'isomorphic-validation';
import isStrongPassword from '../validators/is-strong-password.js';

const passwordV = Validation().constraint(isStrongPassword);

export default passwordV;










Here I'm creating two Validation objects for e-mail and password fields with validators that will be shared by both forms. We do not duplicate the validation logic even though the sign-up form differs from the sign-in form in the way that it requires additional validators for checking an e-mail registration and checking password and password confirmation equality. We will add them in the following steps.



At this point our dependency graph looks like this:



validations dependency graph






3. Creating UI effects






.
⊞─── public
├── repository.js
⊟─── validation
⊞── profiles
⊟── ui
│ └── apply-effects.js
⊞── validations
⊞── validators







validation/ui/apply-effects.js




// validation/ui/apply-effects.js

import { Validation } from 'isomorphic-validation';
import { applyAccess, applyOutline, applyBox, renderFirstError, renderProperty } from 'isomorphic-validation/ui';

const delayedOutline = {
false: { delay: 2000, value: '2px solid lightpink' },
true: { delay: 500, value: '' },
};

const delayedAccess = {
true: { delay: 600 },
};

const disabledAccess = {
false: { value: true },
true: { value: true },
};

const remainedOutline = {
false: { delay: 20000, value: '' },
true: { delay: 20000, value: '' },
};

const changedOutline = {
false: { delay: 500, value: '2px solid lightpink' },
true: { delay: 500, value: '' },
};

const editIcon = {
false: { value: '🖊' },
true: { value: '🖊' },
position: 'LEVEL_RIGHT',
};

const loadImg = `
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDUwIDUwIj4KICA8Y2lyY2xlIGN4PSIyNSIgY3k9IjI1IiByPSIyMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjBBNUZBIiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWRhc2hhcnJheT0iNjAgMTIwIj4KICAgIDxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiBmcm9tPSIwIDI1IDI1IiB0bz0iMzYwIDI1IDI1IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlVHJhbnNmb3JtPgogIDwvY2lyY2xlPgo8L3N2Zz4=" alt="SVG Image" />
`
;

const loadIcon = {
false: { delay: 1000, value: loadImg },
true: { delay: 1000, value: loadImg },
position: 'LEVEL_RIGHT',
};

const validIcon = {
false: { delay: 1000, value: '' },
true: { delay: 500, value: '' },
position: 'LEVEL_RIGHT_BESIDE',
style: { color: 'green', left: '-8px' },
};

const errMsg = {
false: { delay: 2000, value: renderFirstError('err') },
position: 'BELOW_CENTER',
mode: 'MAX_SIDE',
style: { color: 'firebrick', fontSize: '12px', padding: '4px' },
};

const changedMsg = {
false: { delay: 500, value: renderFirstError('err') },
position: 'BELOW_CENTER',
mode: 'MAX_SIDE',
style: { color: 'firebrick', fontSize: '12px', padding: '4px' },
};

const waitMsg = {
false: { delay: 1000, value: renderProperty('waitMsg') },
true: { delay: 1000, value: renderProperty('waitMsg') },
position: 'BELOW_CENTER',
mode: 'MAX_SIDE',
style: { color: 'steelblue', fontSize: '12px', padding: '4px' },
};

const applyEffects = validationProfile => {
const [form, groupingValidation] = validationProfile;

form.addEventListener(
'input',
Validation.group(
groupingValidation.validations.map((validation, idx) => {
const iconEID = form[idx].name + 'icon';
const errMsgEID = form[idx].name + 'error';
const outlineEID = form[idx].name + 'outline';
const formField = form[idx].parentNode;

return validation
.started(applyOutline(formField, remainedOutline, outlineEID))
.validated(applyOutline(formField, delayedOutline, outlineEID))
.changed(applyOutline(formField, changedOutline, outlineEID))
.started(applyBox(formField, editIcon, iconEID))
.started(applyBox(formField, loadIcon, iconEID))
.validated(applyBox(formField, validIcon, iconEID))
.changed(applyBox(formField, validIcon, iconEID))
.started(applyBox(formField, waitMsg, errMsgEID))
.validated(applyBox(formField, errMsg, errMsgEID))
.changed(applyBox(formField, changedMsg, errMsgEID));
}),
)
.started(applyAccess(form.submitBtn, disabledAccess))
.validated(applyAccess(form.submitBtn, delayedAccess)),
);
};

export default applyEffects;










Here I simply wrap the effects I demonstrated in the previous post's example in a function in order to apply them to both forms. Insted of using the library's UI effect functions, you can also create your own effects, make them a part of your components and use Validation or Predicate objects inside your components to connect validity states to your components' states.






4. Creating validation profiles






.
⊟── public
├── repository.js
⊟── validation
⊟── profiles
│ ├── signin.js
│ └── signup.js
⊞─── ui
⊞─── validations
⊞─── validators







validation/profiles/signin.js




// validation/profiles/signin.js

import { Validation } from 'isomorphic-validation';
import emailV from '../validations/email.js';
import passwordV from '../validations/password.js';
import applyEffects from '../ui/apply-effects.js';

/** Creating profiles */

const [signinForm, signinV] = Validation.profile('[name="signinForm"]', ['email', 'password'], [emailV, passwordV]);

/** UI effects */

applyEffects([signinForm, signinV]);

export { signinForm, signinV };











validation/profiles/signup.js




// validation/profiles/signup.js

import { Validation } from 'isomorphic-validation';
import emailV from '../validations/email.js';
import passwordV from '../validations/password.js';
import isPasswordConfirmed from '../validators/is-password-confirmed.js';
import isEmailNotRegistered from '../validators/is-email-not-registered.js';
import applyEffects from '../ui/apply-effects.js';

const pwdConfirm = Validation();

/** Creating profiles */

const [signupForm, signupV] = Validation.profile(
'[name="signupForm"]',
['email', 'password', 'pwdConfirm'],
[emailV, passwordV, pwdConfirm],
);

signupV.email.client.constraint(isEmailNotRegistered, { debounce: 5000 });

Validation.glue(signupV.password, signupV.pwdConfirm).constraint(isPasswordConfirmed);

/** UI effects */

applyEffects([signupForm, signupV]);

export { signupForm, signupV };










Here signin.js and signup.js will be entry points for a module bundler. This is the place where we apply UI effects. Also, signup.js is the place to add sign-up form specific validators:




// this validator will be added on the client side only
signupV.email.client.constraint(isEmailNotRegistered, { debounce: 5000 });

Validation.glue(signupV.password, signupV.pwdConfirm)
.constraint(isPasswordConfirmed);






Now our dependency graph has the following structure:



profiles dependency graph



We are using the same validations on both forms without duplicating.






5. Creating the server






.
├── index.js
⊞─── public
├── repository.js
⊟── validation
⊞─── profiles
⊞─── ui
⊞─── validations
⊞─── validators







index.js




// index.js

import express from 'express';
import bodyParser from 'body-parser';
import { signinV } from './validation/profiles/signin.js';
import { signupV } from './validation/profiles/signup.js';
import isEmailNotRegisteredS from './validation/validators/is-email-not-registered-s.js';

const app = express();
const urlencodeParser = bodyParser.urlencoded({ extended: false });

app.use(express.static('public'));

function signinHandler(req, res) {
const { validationResult } = req;

if (validationResult.isValid) {
// check credentials and authorize the user
// ...
res.send('VALID');
} else {
// respond with the validation error
// ...
res.json(validationResult);
}
}

function signupHandler(req, res) {
const { validationResult } = req;

if (validationResult.isValid) {
// create an account
// ...
res.send('VALID');
} else {
// respond with the validation error
// ...
res.json(validationResult);
}
}

function checkemailHandler(req, res) {
res.json(req.validationResult.isValid);
}

const emailV = signupV.email.server.constraint(isEmailNotRegisteredS);

// validations are added as middlewares
app.post('/signin', urlencodeParser, signinV, signinHandler);
app.post('/signup', urlencodeParser, signupV, signupHandler);
app.post('/checkemail', urlencodeParser, emailV, checkemailHandler);

app.listen(8080, () => {
console.log('Listening port 8080 ...');
});










On the server side, we use validations as Express middleware functions:




// here the .server property actually can be ommited
const emailV = signupV.email.server.constraint(isEmailNotRegisteredS);

// validations are added as middleware functions
app.post('/signin', urlencodeParser, signinV, signinHandler);
app.post('/signup', urlencodeParser, signupV, signupHandler);
app.post('/checkemail', urlencodeParser, emailV, checkemailHandler);









Final result



This is the final project's file system structure:




.
├── index.js
⊟── public
│ ⊞─── bundles
│ ├── signin.html
│ ├── signup.html
│ └── style.css
├── repository.js
⊟── validation
⊟── profiles
│ ├── signin.js
│ └── signup.js
⊟── ui
│ └── apply-effects.js
⊟── validations
│ ├── email.js
│ └── password.js
⊟── validators
├── is-email.js
├── is-email-not-registered.js
├── is-email-not-registered-s.js
├── is-max-length.js
├── is-min-length.js
├── is-password-confirmed.js
└── is-strong-password.js






The final dependency graph looks as the following:



final dependency graph






Conclusion



What we achived so far:




  • No duplicates of validation logic across different forms with the same kind of fields.


  • No duplicates of validation logic between client and server.


  • All validation logic resides in one place which provides one source of truth.




If you read up to this point I'd like to know your opinion. What do you think about this approach? Does this library have the right to exist?

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Using the same validation logic on the client and server side with Isomorphic-validation.

Thematisch verwandte Begriffe: Using, same, validation, logic · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94097 | A vulnerability was determined in Netcore NBR200V2 1.3.241127.071246. Th…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick