Using and Extending Suvidha’s Default Handlers

Suvidha’s default handlers offer a quick start for request/response management. They handle common tasks and can be easily extended.

Introduction

Default handlers provide a pre-configured setup. They handle response formatting, errors, and successful requests, allowing you to focus on your logic. They’re ready to use yet customizable.

Basic Usage

import express from "express";
import { Suvidha, DefaultHandlers, Http } from "suvidha";

const app = express();
app.use(express.json());

const suvidha = () => Suvidha.create(new DefaultHandlers());

app.get(
    "/hello",
    suvidha().handler(() => {
        return Http.Ok.body({ message: "Hello, world!" });
    }),
);

// Example using a plain Protocol object:
app.get(
    "/simple",
    suvidha().handler(() => {
        return { status: 200, body: { message: "This is simple." } };
    }),
);

app.listen(3000, () => console.log("Server listening on port 3000"));

Default handlers are created with new DefaultHandlers(). suvidha() uses them to process requests. Your handler function contains your logic. Return data, use Http classes, or plain Protocol objects for responses.

Extending

Extend the default handlers to customize behavior. Create a class that inherits from DefaultHandlers and overrides its methods.

import { Handlers, Conn } from "suvidha";
import { DefaultHandlers } from "./defaultHandlers";

export class SimpleCustomHandlers extends DefaultHandlers implements Handlers {
    override onComplete(output: unknown, conn: Conn, next: NextFunction) {
        // Add custom headers to all responses
        conn.res.setHeader("X-API-Version", "2.0");

        // Preserve default behavior
        super.onComplete(output, conn, next);
    }

    override onErr(err: unknown, conn: Conn, next: NextFunction) {
        // Simple error logging
        console.error("[Custom Handler Error]", err);

        // Custom error format
        conn.res.status(500).json({
            error: "Something went wrong",
            reference: Date.now().toString(36),
        });
    }
}

User Profile Endpoint with SimpleCustomHandlers

app.get(
    "/users/:id",
    Suvidha.create(new SimpleCustomHandlers())
        .params(z.object({ id: z.string().uuid() }))
        .use(async (req) => {
            const user = await getUser(req.params.id);
            if (!user) throw Http.NotFound.body("User not found");
            return { user };
        })
        .handler((req) => {
            return {
                id: req.context.user.id,
                name: req.context.user.name,
                stats: {
                    logins: req.context.user.loginCount,
                },
            };
        }),
);
{
    "status": "success",
    "data": {
        "id": "usr_987",
        "name": "Alice Smith",
        "stats": {
            "logins": 42
        }
    },
    "meta": {}
}
HTTP/1.1 200 OK
X-API-Version: 2.0

Customization

Logging

Override onComplete or onErr.

class MyHandlers extends DefaultHandlers {
    override onComplete(
        output: unknown,
        conn: Conn,
        next: NextFunction,
    ): Promise<void> | void {
        console.log(
            `Request completed. Output: ${JSON.stringify(output)}, URL: ${conn.req.originalUrl}`,
        );
        return super.onComplete(output, conn, next);
    }

    override onErr(
        err: unknown,
        conn: Conn,
        next: NextFunction,
    ): Promise<void> | void {
        console.error(`Error: ${err}, URL: ${conn.req.originalUrl}`);
        return super.onErr(err, conn, next);
    }
}

Setting Headers

Override onComplete or onErr.

class MyHandlers extends DefaultHandlers {
    override onComplete(
        output: unknown,
        conn: Conn,
        next: NextFunction,
    ): Promise<void> | void {
        conn.res.setHeader("Cache-Control", "no-cache");
        conn.res.setHeader("X-API-Version", "2.0");
        return super.onComplete(output, conn, next);
    }
}

Using next(err) (Within Suvidha Handlers)

Suvidha handlers receive the next function, allowing you to pass errors to Express.js error handling middleware or the default Express.js error handler. This is particularly useful if you have existing error handling logic in Express.js that you want to leverage.

import { DefaultHandlers, Conn, Http } from "suvidha";

class MyHandlers extends DefaultHandlers {
    override onErr(
        err: unknown,
        conn: Conn,
        next: NextFunction,
    ): Promise<void> | void {
        if (err instanceof Http.End) {
            return super.onErr(err, conn, next); // Default Suvidha error handling
        }

        return next(err); // Delegate to Express.js error handling
    }
}

Custom Error Handling

Override onErr.

import { Http } from "suvidha"; // Import Http

class MyHandlers extends DefaultHandlers {
    override onErr(
        err: unknown,
        conn: Conn,
        next: NextFunction,
    ): Promise<void> | void {
        if (err instanceof Http.NotFound) {
            return void conn.res
                .status(404)
                .send({ error: "Resource not found" });
        } else if (
            err instanceof Error &&
            err.message.includes("Database Error")
        ) {
            return void conn.res
                .status(500)
                .send({ error: "Database error occurred." });
        }
        return super.onErr(err, conn, next);
    }
}

Modifying Response Format

Override the formatter.

class MyHandlers extends DefaultHandlers {
    constructor() {
        const fmt = (status, body, meta) => {
            return {
                statusCode: status,
                data: body,
                metadata: meta,
            };
        };
        super(fmt);
    }
}

Middleware Integration

Use Suvidha’s .use() method for middleware, which preserves type safety and allows context propagation.

import { Suvidha, DefaultHandlers, Http } from "suvidha";
import { z } from "zod"; // Import Zod

interface User {
    id: string;
    role: string;
}

declare function authenticate(req: Request): Promise<User>;

const suvidha = () => Suvidha.create(new DefaultHandlers());

const auth = () =>
    suvidha().use(async (req) => {
        const user = await authenticate(req).catch((_) => {
            throw new Http.Unauthorized();
        });
        return { user };
    });

const adminOnly = () =>
    auth().use(async (req) => {
        if (req.context.user.role !== "admin") {
            throw new Http.Forbidden();
        }
        return {};
    });

const userSchema = z.object({
    name: z.string(),
    email: z.string().email(),
});

const createUser = (user: User) => Http.Created.body(user);

app.post(
    "/users",
    auth()
        .body(userSchema)
        .handler((req) => {
            const user = req.body;
            return createUser(user);
        }),
);

const pageSchema = z.object({
    page: z.string().transform(Number),
    limit: z.string().transform(Number),
});

app.get(
    "/users",
    auth()
        .query(pageSchema)
        .handler((req) => {
            const { page, limit } = req.query;
            return Http.Ok.body({ page, limit });
        }),
);

app.listen(3000, () => console.log("Server is listening on port 3000")); // Added a port and listen call