Most people want to build APIs or internal tools to return response messages in a consistent structure. For example, response like success
, error
, or warning
follow a similar shape but differing only in status.
Some basic response message formats:
{
status: "success",
message: "user details updated successfully!"
}
{
status: "error",
message: "Oops! something went wrong."
}
These functions can be written in a very simple way that anyone can understand it easily (check some examples below). But this often leads to code redundancy, and lack of flexibility.
function success(message) {
return { status: "success", message };
}
function error(message) {
return { status: "error", message };
}
As you can see, this approach is hard to maintain, if you want to make some changes in the structure. You will have to update every function to keep the structure consistent.
To make this easy, I have introduced a solution using Proxy
object in JavaScript. But before this, let's understand how Proxy
works.
What is Proxy?
In JavaScript, Proxy
is a powerful feature that allows you to customize the behaviour of operations performed on objects. Like property access, assignment, enumeration, and more.
A Proxy wraps an object and let you allow to redefine how basic operations work on that object. It takes two arguments:
const proxy = new Proxy(target, handler);
-
target
: the original object you want to wrap (can be empty object{}
). -
handler
: an object that defines functions (called traps) for intercepting operations on the target object likeget
,set
,apply
, etc.
Basic Example
Here's a simple Proxy
that returns a default value ("N/A"
) when trying to access a property that doesn't exist.
const themeSettings = { theme: "light", color: "blue" };
const themeWithDefault = new Proxy(themeSettings,
{
get: (target, prop) =>
prop in target ? target[prop] : "N/A"
})
console.log(themeWithDefault.theme); // Output: 'light'
console.log(themeWithDefault.fontSize); // Output: 'N/A'
In this example you can see
get
trap intercepts property access.
Proxy
Based Solution
Let's understand how we can use a Proxy
to create dynamic response messages.
function createResponseMessage(message) {
return new Proxy(
{},
{
get: (_, status) => () => ({
status,
message,
}),
}
);
}
In this function:
-
createResponseMessage
returns a proxy object. - Accessing any key like
.success
,.error
, etc., returns a consistent response object withmessage
andstatus
properties.
How to use this?
const res = createResponseMessage(
"User updated successfully"
).success();
console.log(res);
// Output: { status: "success", message: "User updated successfully" }
As you can see, this approach creates a consistent response object in a clean, DRY, and elegant manner.
However, there is one major problem. To understand it better, let's look at another example:
const res = createResponseMessage(
"Wrong status created!"
).problem();
console.log(res);
// Output: { status: "problem", message: "Wrong status created!" }
Did you notice the issue?
That's right! you can create any random status by accessing random property.
There are two way to fix this issue.
First, we can add a check to verify the status and throw an error if user tries to call it with an unsupported one.
function createResponseMessage(message) {
const validStatuses = ["success", "info", "warning", "error"];
return new Proxy(
{},
{
get: (_, status) => () => {
if (!validStatuses.includes(status)) {
throw new Error("Invalid status!");
}
return ({
status,
message,
})
},
}
);
}
This solution works but it is no longer as clean and elegant. Plus, you won't get suggestions for valid statuses. You will have to remember everything.
Right? So let's move on to the next solution.
Most developers today use TypeScript in their projects and our next solution takes full advantage of it.
To make it work, we will define two types: MessageStatus
and ResponseMessage
.
// type to define valid statuses
type MessageStatus = "success" | "info" | "warning" | "error";
// type to define the response structure
type ResponseMessage = {
status: MessageStatus;
message: string;
}
Use these specified types in our solution:
function createResponseMessage(message: string) {
return new Proxy(
{},
{
get: (_, status: MessageStatus) => (): ResponseMessage => ({
status,
message,
}),
}
) as Record<MessageStatus, () => ResponseMessage>;
}
Now you get full autocomplete and type safety.
This approach also helps to make your code cleaner and more expressive. You should also experiment with Proxy
for handling dynamic API calls.
I would love to hear your thoughts! Let me know in comments.
If you have questions, feedback, or just want to connect, feel free to reach me here:
- Github: https://github.com/yoku2010
- LinkedIn: https://www.linkedin.com/in/yoku2010/
Always happy to talk about JavaScript, TypeScript, or clean code practices!
Thanks for reading!
Top comments (0)