Build a simple Mini Day.js Clone & Logger Middleware by Call Signatures in TypeScript

Published: (February 4, 2026 at 12:15 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

First, understand that a function is an object

You should know that the typeof a function prototype is object — I’m referring to the prototype, not typeof function itself.

If you are not familiar with prototypes, I strongly suggest reading about them first before continuing.

The problem we want to solve

How can we configure a function and then execute it with those configurations?

This is somewhat similar to a closure, but closures encapsulate state using variable scope. With a call signature, the state is exposed outside — meaning we can mutate the properties if needed.

You might encounter this pattern in libraries like dayjs or when implementing a logger middleware. How do we define it properly in TypeScript?

Call Signatures in TypeScript

In JavaScript, functions can have properties in addition to being callable. However, the typical function type syntax does not allow declaring properties. If we want to describe something callable with properties, we can use a call signature inside an object type.

A call signature is similar to a closure, but the key difference is that the state lives outside the function, so it can be modified.

Example 1 — Mini Day.js Clone

type DayJS = {
  timezone: string;
  description: string;
  (date: string | number | Date): string;
};

const dayjs = function (date: string | number | Date) {
  const tz = dayjs.timezone;

  const formatter = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });

  return formatter.format(new Date(date));
} as DayJS;

dayjs.timezone = "Asia/Ho_Chi_Minh";
dayjs.description = "Mini DayJS clone";

console.log(dayjs("2026-02-04"));
// 02/04/2026, 07:00:00 AM

Here, dayjs is both:

  • callable like a function
  • configurable via properties

This is exactly what a call signature enables.

Example 2 — Logger Middleware

Now let’s implement a logger middleware with configurable properties such as:

  • isEnabled (based on production env)
  • log type (warning, error, info)
  • message payload
type LoggerData = { message: string; metadata?: unknown };

type LoggerType = "warning" | "error" | "info";

type LoggerMiddleware = {
  isEnabled: boolean;
  type: LoggerType;
  (data: LoggerData): void;
};

const initializedSomeService = () => {
  const warningLogger: LoggerMiddleware = function (data) {
    if (!warningLogger.isEnabled) return;

    console.warn(`TYPE:${warningLogger.type}`, { data });
  };

  // configure properties
  warningLogger.isEnabled = process.env.NODE_ENV === "production";
  warningLogger.type = "warning";

  // execute like a function
  warningLogger({
    message: "This is a warning message",
    metadata: { id: "ab85592d-b7cf-4623-9884-aa70f40814f7" },
  });
};

initializedSomeService();

Result:

TYPE:warning {
  data: {
    message: 'This is a warning message',
    metadata: { id: 'ab85592d-b7cf-4623-9884-aa70f40814f7' }
  }
}

When should you use call signatures?

Use them when you want something that is:

  • ✅ callable like a function
  • ✅ configurable via properties
  • ✅ strongly typed

Typical real‑world use cases:

  • logger utilities
  • analytics trackers
  • feature‑flag executors
  • SDK‑style APIs (e.g., libraries like dayjs)

Closure vs Call Signature (Quick Insight)

ClosureCall Signature
State handlingEncapsulates stateExposes state
MutabilitySafer from mutationMutable
StyleMore functionalMore library‑style
Common patternsHooksSDK design

Neither is better — choose based on your design goal.

  • If you want immutability → prefer closures.
  • If you want configurability → call signatures shine.

Final Thoughts

Call signatures are a small TypeScript feature, but once you understand them, you’ll start noticing this pattern in many libraries. They provide a powerful way to design APIs that feel natural to JavaScript — configurable, callable, and strongly typed.

Definitely worth adding to your TypeScript toolbox 🚀

Back to Blog

Related posts

Read more »

TypeScript or Tears

Frontend Quality Gates See also: Backend Quality Gates Backend linters catch async footguns. Type checkers prevent runtime explosions. Now it’s the frontend’s...

Deno Sandbox

Article URL: https://deno.com/blog/introducing-deno-sandbox Comments URL: https://news.ycombinator.com/item?id=46874097 Points: 57 Comments: 9...