Steve Kinney

Full-Stack TypeScript

Branded Types with Express

Sometimes basic types aren’t enough. For IDs, tokens, and other special strings, we can use branded types:

// Define branded types
type UserId = string & { readonly _brand: unique symbol };
type SessionToken = string & { readonly _brand: unique symbol };

// Create functions to safely create branded types
function createUserId(id: string): UserId {
	return id as UserId;
}

function createSessionToken(token: string): SessionToken {
	return token as SessionToken;
}

// Use in request and response types
interface GetUserRequest {
	params: {
		userId: UserId;
	};
}

interface UserResponse {
	id: UserId;
	username: string;
	email: string;
}

interface AuthResponse {
	token: SessionToken;
	user: UserResponse;
}

// Example usage
app.get('/users/:userId', (req: Request<GetUserRequest['params']>, res: Response<UserResponse>) => {
	const rawUserId = req.params.userId;

	// Convert string to branded type
	const userId = createUserId(rawUserId);

	// Now we have a type-safe userId that can't be confused with other string IDs
	const user = getUserById(userId);

	res.json(user);
});

app.post('/login', (req: Request, res: Response<AuthResponse>) => {
	// Generate a session token
	const token = createSessionToken(generateRandomToken());

	// Get user
	const userId = createUserId('123');
	const user = getUserById(userId);

	// Return typed response
	res.json({
		token,
		user,
	});
});

Branded types ensure that you don’t accidentally mix up different types of IDs or tokens, even though they’re all strings underneath.

Last modified on .