

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: Data Leaks
incomplete
2: Sanitizing Error Messages
incomplete
3: Global Error Handling
incomplete
4: Sanitizing Logs
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
An endpoint can pass every authorization check and still reveal more data than it should. Imagine an online bookstore that sends its full inventory records to every shopper:
// broken: exposes complete database records
app.get("/api/books", (_req: Request, res: Response) => {
res.json({ books: listAllBooks() });
});
Those records might include supplier costs, unpublished titles, acquisition notes, and internal audit fields. The storefront didn't need any of that.
Maybe the endpoint was safe when the table it was reading from only contained public fields. But as the data model grows, every endpoint that returns the whole record inherits its new private data. Oopsie.
You should explicitly control what crosses the server-to-client boundary! A typed response shape gives you a stable public contract that doesn't grow just because the database model does:
interface BookResponse {
id: number;
title: string;
author: string;
price_cents: number;
}
function toBookResponse(book: Book): BookResponse {
return {
id: book.id,
title: book.title,
author: book.author,
price_cents: book.price_cents,
};
}
// fixed: returns an explicit public representation
app.get("/api/books", (_req: Request, res: Response) => {
const books = listPublishedBooks();
res.json({ books: books.map(toBookResponse) });
});
Return the fields the client is allowed to see, not the object you happen to store in your database.
Bearly Secure's public product and customer order APIs return complete database records. Return explicit public response shapes instead.
type ProductResponse = {
id: number;
name: string;
description: string;
image_path: string;
price_cents: number;
};
type OrderResponse = {
id: number;
status: Order["status"];
total_cents: number;
created_at: string;
};
type OrderItemResponse = {
product_id: number;
product_name: string;
quantity: number;
price_cents: number;
};
Order["status"] needs the Order type imported from ../orders/index.ts. The Product and OrderItem types come from the same modules as their query functions.
With Bearly Secure still running, run and submit the CLI tests from the project root.