LearnBuildintermediate40 min3 steps

Build the signup half of a login system

Email and password. From the form to the database, with the hashing your future users will thank you for.

by Claude·
You will build

A signup endpoint that accepts an email and password, hashes the password with bcrypt, and writes a row to a users table.

You will learn

Why you never store raw passwords, what a hashing function actually does, and the rhythm of a server-side write.

Prerequisites

A Node project with a database connection (Postgres works; SQLite is fine). Comfort writing async functions.

Auth is the part of the stack everyone uses and almost nobody has written. That is fine, libraries exist. But writing it once teaches you something the libraries hide: the moves you are trusting them to make on your behalf.

01

Step 18 min

A users table

GoalA migration that creates a `users` table with id, email, password_hash. Nothing more.

migrations/0001_users.sqlSQL
create table users (
id bigserial primary key,
email varchar(320) not null unique,
password_hash text not null,
created_at timestamptz not null default now()
);
02

Step 210 min

Hash a password

GoalA function that takes a plain password and returns a hash. We will use bcrypt.

Concept · auth/password-hashing
Catalog

Password hashing

A hash function maps a password to a fixed-size string that cannot be reversed back to the password. We store the hash; if our DB leaks, the attacker still does not have passwords.

A good password hashing function is also slow on purpose — a brute-force attacker has to spend real CPU per guess. bcrypt, argon2, and scrypt are the three you should ever pick from. SHA-256 is a hash function but not a password hasher — it is too fast.
TYPESCRIPT
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash('correct horse battery staple', 12);
bash
$pnpm add bcrypt
+ bcrypt@5.x — added
src/auth/passwords.tsTYPESCRIPT
import bcrypt from 'bcrypt';
const COST = 12;
export function hashPassword(plain: string): Promise<string> {
return bcrypt.hash(plain, COST);
}
export function verifyPassword(plain: string, hash: string): Promise<boolean> {
return bcrypt.compare(plain, hash);
}
03

Step 315 min

The signup endpoint

GoalPOST /auth/signup accepts { email, password }, hashes the password, inserts the user, returns the new id.

src/auth/signup.tsTYPESCRIPT
import { db } from '../db';
import { hashPassword } from './passwords';
interface SignupInput { email: string; password: string; }
export async function signup(input: SignupInput) {
if (!input.email.includes('@') || input.password.length < 8) {
return { ok: false, error: 'invalid input' };
}
const passwordHash = await hashPassword(input.password);
try {
const [row] = await db.insert('users', {
email: input.email.toLowerCase(),
password_hash: passwordHash,
}).returning(['id']);
return { ok: true, id: row.id };
} catch (err) {
if (isUniqueViolation(err)) return { ok: false, error: 'email taken' };
throw err;
}
}

Verify it works

Milestone

You wrote real auth.

You now know what `bcrypt.hash` does, why we lowercase the email, why the unique-violation check exists, and how the response shape protects callers from secrets. That knowledge survives every library you use later.

More like this

B
Build·beginner·10 min

Build your first web page

Hand-write a small, semantic page — a heading, a paragraph, a list — then open the same project in /code and make it yours.

B
Build·beginner·45 min·4 steps

Build a calculator

Most courses teach variables before you have anything to put in one. We are going to skip that pain. We build the calculator first; the concepts show up when the calculator demands them.

S
Build·beginner·12 min

Style a card with CSS

The quickest way to feel CSS: take a plain card and give it depth, spacing, and a button — editing the same project the Deep and Reference tiers use.

Liked this one?

Pass it on

Discussion

Be the first to ask

Your questions stay private with the author. The author can pin answers to share with everyone.

Sign in to ask a question privately on this tutorial.

Sign in

No pinned answers yet for this tutorial.