npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mvc-front-sdk

v0.1.9

Published

MVC Front SDK - A modern frontend MVC SDK

Readme

MVC Front SDK

A framework-agnostic MVC SDK for building frontend applications with clean architecture, type safety, and dependency injection support. Works seamlessly with any JavaScript/TypeScript framework including React, Vue, Angular, Svelte, and more.

What is MVC Front SDK?

MVC Front SDK provides a clean, type-safe way to organize your API logic using the Model-View-Controller pattern. It's designed to work with any frontend framework - whether you're building with React, Vue, Angular, Next.js, Nuxt.js, or any other framework, the SDK adapts to your needs.

Why MVC Pattern?

  • Separation of Concerns: Keep your API logic separate from your UI components
  • Reusability: Share controllers across different parts of your application
  • Testability: Easily test your API logic in isolation
  • Type Safety: Full TypeScript support with generics for compile-time safety
  • Maintainability: Clean, organized code that's easy to understand and modify

Features

  • 🎯 Framework-Agnostic: Works with any JavaScript/TypeScript framework
  • 🔒 Type-Safe: Full TypeScript support with generics
  • 🔐 Authentication Ready: Optional token-based authentication
  • 🏗️ Dependency Injection: Built on tsyringe for clean architecture
  • 🚀 Simple API: Easy-to-use controller pattern
  • 📦 Lightweight: Minimal dependencies
  • 🔄 Auto Method Binding: Automatic this context binding
  • 🛠️ Helper Methods: Built-in utilities for URL construction and query parameters

Installation

npm install mvc-front-sdk
# or
yarn add mvc-front-sdk
# or
bun add mvc-front-sdk
# or
pnpm add mvc-front-sdk

Requirements

  • Node.js >= 18.0.0
  • TypeScript ^5

Quick Start

Basic Controller

import { BaseController } from "mvc-front-sdk";

interface User {
  id: string;
  name: string;
  email: string;
}

export class UserController extends BaseController {
  constructor() {
    super("https://api.example.com");
  }

  async getUsers() {
    try {
      const users = await this.apiService.get<User[]>("/users");
      return users;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getUserById(id: string) {
    try {
      const user = await this.apiService.get<User>(`/users/${id}`);
      return user;
    } catch (error) {
      this.handleError(error);
    }
  }
}

With Authentication

export class AuthenticatedController extends BaseController {
  constructor() {
    super("https://api.example.com", process.env.API_TOKEN);
    // All requests automatically include: Authorization: Bearer <token>
  }

  async getProfile() {
    const profile = await this.apiService.get<User>("/profile");
    return profile;
  }
}

With Default Headers

You can provide default headers that will be included in all requests. Custom headers passed to individual API methods will override default headers.

export class ApiController extends BaseController {
  constructor() {
    super("https://api.example.com", process.env.API_TOKEN, {
      "X-API-Version": "v1",
      "X-Client-ID": "client-123",
      "X-Custom-Header": "value",
    });
    // All requests automatically include:
    // - Authorization: Bearer <token>
    // - X-API-Version: v1
    // - X-Client-ID: client-123
    // - X-Custom-Header: value
  }

  async getUsers() {
    // This request will include all default headers + token
    const users = await this.apiService.get<User[]>("/users");
    return users;
  }

  async getUserWithCustomHeaders(id: string) {
    // Custom headers override defaults
    const user = await this.apiService.get<User>(`/users/${id}`, {
      "X-Custom-Header": "overridden-value", // Overrides default
      "X-Request-ID": "123", // New header added
    });
    // Final headers:
    // - Authorization: Bearer <token>
    // - X-API-Version: v1 (from defaults)
    // - X-Client-ID: client-123 (from defaults)
    // - X-Custom-Header: overridden-value (overridden)
    // - X-Request-ID: 123 (new)
    return user;
  }
}

Framework Integration

The SDK is designed to work seamlessly with any framework. Below are examples showing how to integrate it with popular frameworks.

NuxtJS

NuxtJS provides composables and server-side rendering capabilities. You can use MVC Front SDK controllers with Nuxt composables for a clean integration.

Installation

npm install mvc-front-sdk

Controller Setup

// composables/controllers/user.controller.ts
import { BaseController } from "mvc-front-sdk";

interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserDto {
  name: string;
  email: string;
}

export class UserController extends BaseController {
  constructor() {
    const config = useRuntimeConfig();
    super(config.public.apiBaseUrl, config.apiToken);
  }

  async getAllUsers(filters?: { page?: number; limit?: number }) {
    try {
      const queryParams = this.buildSearchParams(filters);
      const url = this.createURL("/users", queryParams);
      const users = await this.apiService.get<User[]>(url);
      return users;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getUserById(id: string) {
    try {
      const user = await this.apiService.get<User>(`/users/${id}`);
      return user;
    } catch (error) {
      this.handleError(error);
    }
  }

  async createUser(userData: CreateUserDto) {
    try {
      const newUser = await this.apiService.post<User>("/users", userData);
      return newUser;
    } catch (error) {
      this.handleError(error);
    }
  }
}

Composable Pattern

// composables/useUsers.ts
import { UserController } from "./controllers/user.controller";

let userControllerInstance: UserController | null = null;

function getUserController() {
  if (!userControllerInstance) {
    const config = useRuntimeConfig();
    userControllerInstance = new UserController();
  }
  return userControllerInstance;
}

export const useUsers = () => {
  const controller = getUserController();

  const users = useState<User[]>("users", () => []);
  const loading = useState<boolean>("users-loading", () => false);
  const error = useState<Error | null>("users-error", () => null);

  const fetchUsers = async (filters?: { page?: number; limit?: number }) => {
    loading.value = true;
    error.value = null;
    try {
      const data = await controller.getAllUsers(filters);
      users.value = data;
      return data;
    } catch (err) {
      error.value = err as Error;
      throw err;
    } finally {
      loading.value = false;
    }
  };

  const fetchUser = async (id: string) => {
    loading.value = true;
    error.value = null;
    try {
      const user = await controller.getUserById(id);
      return user;
    } catch (err) {
      error.value = err as Error;
      throw err;
    } finally {
      loading.value = false;
    }
  };

  const createUser = async (userData: CreateUserDto) => {
    loading.value = true;
    error.value = null;
    try {
      const newUser = await controller.createUser(userData);
      users.value.push(newUser);
      return newUser;
    } catch (err) {
      error.value = err as Error;
      throw err;
    } finally {
      loading.value = false;
    }
  };

  return {
    users: readonly(users),
    loading: readonly(loading),
    error: readonly(error),
    fetchUsers,
    fetchUser,
    createUser,
  };
};

Server-Side Rendering Usage

// pages/users/index.vue
<template>
  <div>
    <h1>Users</h1>
    <div v-if="pending">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <div v-else>
      <div v-for="user in data" :key="user.id">
        {{ user.name }} - {{ user.email }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
const { data, pending, error } = await useLazyAsyncData('users', () => {
  const controller = new UserController();
  return controller.getAllUsers({ page: 1, limit: 10 });
});
</script>

Client-Side Usage

// pages/users/[id].vue
<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="userError">Error: {{ userError.message }}</div>
    <div v-else-if="user">
      <h1>{{ user.name }}</h1>
      <p>{{ user.email }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
const route = useRoute();
const { fetchUser, loading, error: userError } = useUsers();

const user = ref<User | null>(null);

onMounted(async () => {
  user.value = await fetchUser(route.params.id as string);
});
</script>

Tanstack Start

Tanstack Start is a full-stack React framework. You can use MVC Front SDK controllers in route loaders, server actions, and React components.

Installation

npm install mvc-front-sdk

Controller Setup

// app/controllers/user.controller.ts
import { BaseController } from "mvc-front-sdk";

interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserDto {
  name: string;
  email: string;
}

export class UserController extends BaseController {
  constructor() {
    super(process.env.API_BASE_URL, process.env.API_TOKEN);
  }

  async getAllUsers(filters?: { page?: number; limit?: number }) {
    try {
      const queryParams = this.buildSearchParams(filters);
      const url = this.createURL("/users", queryParams);
      const users = await this.apiService.get<User[]>(url);
      return users;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getUserById(id: string) {
    try {
      const user = await this.apiService.get<User>(`/users/${id}`);
      return user;
    } catch (error) {
      this.handleError(error);
    }
  }

  async createUser(userData: CreateUserDto) {
    try {
      const newUser = await this.apiService.post<User>("/users", userData);
      return newUser;
    } catch (error) {
      this.handleError(error);
    }
  }
}

Route Loader Usage

// app/routes/users.tsx
import { createFileRoute } from "@tanstack/react-router";
import { UserController } from "../controllers/user.controller";

export const Route = createFileRoute("/users")({
  loader: async ({ search }) => {
    const controller = new UserController();
    const filters = {
      page: search.page || 1,
      limit: search.limit || 10,
    };
    return controller.getAllUsers(filters);
  },
  component: UsersPage,
});

function UsersPage() {
  const users = Route.useLoaderData();

  return (
    <div>
      <h1>Users</h1>
      {users.map((user) => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

Server Actions

// app/actions/user.actions.ts
import { createServerFn } from "@tanstack/start";
import { UserController } from "../controllers/user.controller";

export const createUser = createServerFn()
  .validator((input: CreateUserDto) => input)
  .handler(async ({ data }) => {
    const controller = new UserController();
    return controller.createUser(data);
  });

React Component Integration

// app/components/CreateUserForm.tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { createUser } from "../actions/user.actions";
import { UserController } from "../controllers/user.controller";

export function CreateUserForm() {
  const queryClient = useQueryClient();
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const mutation = useMutation({
    mutationFn: async (userData: CreateUserDto) => {
      // Option 1: Use server action
      return createUser({ data: userData });

      // Option 2: Use controller directly (client-side)
      // const controller = new UserController();
      // return controller.createUser(userData);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
      setName("");
      setEmail("");
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    mutation.mutate({ name, email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Name"
      />
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? "Creating..." : "Create User"}
      </button>
    </form>
  );
}

TanStack Query Integration

// app/hooks/useUsers.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { UserController } from "../controllers/user.controller";

const userController = new UserController();

export function useUsers(filters?: { page?: number; limit?: number }) {
  return useQuery({
    queryKey: ["users", filters],
    queryFn: () => userController.getAllUsers(filters),
  });
}

export function useUser(id: string) {
  return useQuery({
    queryKey: ["user", id],
    queryFn: () => userController.getUserById(id),
    enabled: !!id,
  });
}

export function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (userData: CreateUserDto) =>
      userController.createUser(userData),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
  });
}

Angular

Angular's dependency injection system works perfectly with MVC Front SDK controllers. You can use controllers as Angular services.

Installation

npm install mvc-front-sdk

Service Setup

// src/app/services/user.controller.ts
import { Injectable } from "@angular/core";
import { BaseController } from "mvc-front-sdk";

export interface User {
  id: string;
  name: string;
  email: string;
}

export interface CreateUserDto {
  name: string;
  email: string;
}

@Injectable({
  providedIn: "root",
})
export class UserController extends BaseController {
  constructor() {
    super(environment.apiBaseUrl, environment.apiToken);
  }

  async getAllUsers(filters?: { page?: number; limit?: number }) {
    try {
      const queryParams = this.buildSearchParams(filters);
      const url = this.createURL("/users", queryParams);
      const users = await this.apiService.get<User[]>(url);
      return users;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getUserById(id: string) {
    try {
      const user = await this.apiService.get<User>(`/users/${id}`);
      return user;
    } catch (error) {
      this.handleError(error);
    }
  }

  async createUser(userData: CreateUserDto) {
    try {
      const newUser = await this.apiService.post<User>("/users", userData);
      return newUser;
    } catch (error) {
      this.handleError(error);
    }
  }

  async updateUser(id: string, userData: Partial<User>) {
    try {
      const updatedUser = await this.apiService.put<User>(
        `/users/${id}`,
        userData
      );
      return updatedUser;
    } catch (error) {
      this.handleError(error);
    }
  }

  async deleteUser(id: string) {
    try {
      await this.apiService.delete(`/users/${id}`);
    } catch (error) {
      this.handleError(error);
    }
  }
}

Component Usage

// src/app/components/user-list/user-list.component.ts
import { Component, OnInit } from "@angular/core";
import { CommonModule } from "@angular/common";
import { UserController, User } from "../../services/user.controller";

@Component({
  selector: "app-user-list",
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      <h1>Users</h1>
      <div *ngIf="loading">Loading...</div>
      <div *ngIf="error">Error: {{ error }}</div>
      <div *ngIf="!loading && !error">
        <div *ngFor="let user of users">{{ user.name }} - {{ user.email }}</div>
      </div>
    </div>
  `,
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  loading = false;
  error: string | null = null;

  constructor(private userController: UserController) {}

  async ngOnInit() {
    await this.loadUsers();
  }

  async loadUsers() {
    this.loading = true;
    this.error = null;
    try {
      this.users = await this.userController.getAllUsers({
        page: 1,
        limit: 10,
      });
    } catch (err) {
      this.error = err instanceof Error ? err.message : "An error occurred";
    } finally {
      this.loading = false;
    }
  }
}

Using Async Pipe with Observables

// src/app/services/user.service.ts
import { Injectable } from "@angular/core";
import { Observable, from } from "rxjs";
import { UserController, User, CreateUserDto } from "./user.controller";

@Injectable({
  providedIn: "root",
})
export class UserService {
  constructor(private userController: UserController) {}

  getUsers(filters?: { page?: number; limit?: number }): Observable<User[]> {
    return from(this.userController.getAllUsers(filters));
  }

  getUser(id: string): Observable<User> {
    return from(this.userController.getUserById(id));
  }

  createUser(userData: CreateUserDto): Observable<User> {
    return from(this.userController.createUser(userData));
  }
}
// src/app/components/user-list/user-list.component.ts
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import { UserService } from "../../services/user.service";

@Component({
  selector: "app-user-list",
  standalone: true,
  imports: [CommonModule],
  template: `
    <div>
      <h1>Users</h1>
      <div *ngIf="users$ | async as users">
        <div *ngFor="let user of users">{{ user.name }} - {{ user.email }}</div>
      </div>
    </div>
  `,
})
export class UserListComponent {
  users$ = this.userService.getUsers({ page: 1, limit: 10 });

  constructor(private userService: UserService) {}
}

Form Handling

// src/app/components/create-user/create-user.component.ts
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
import {
  ReactiveFormsModule,
  FormBuilder,
  FormGroup,
  Validators,
} from "@angular/forms";
import { UserController } from "../../services/user.controller";

@Component({
  selector: "app-create-user",
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  template: `
    <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
      <input formControlName="name" placeholder="Name" />
      <input formControlName="email" type="email" placeholder="Email" />
      <button type="submit" [disabled]="userForm.invalid || loading">
        {{ loading ? "Creating..." : "Create User" }}
      </button>
    </form>
  `,
})
export class CreateUserComponent {
  userForm: FormGroup;
  loading = false;

  constructor(private fb: FormBuilder, private userController: UserController) {
    this.userForm = this.fb.group({
      name: ["", Validators.required],
      email: ["", [Validators.required, Validators.email]],
    });
  }

  async onSubmit() {
    if (this.userForm.valid) {
      this.loading = true;
      try {
        await this.userController.createUser(this.userForm.value);
        this.userForm.reset();
      } catch (error) {
        console.error("Error creating user:", error);
      } finally {
        this.loading = false;
      }
    }
  }
}

API Reference

BaseController

The base class that provides API service and helper methods for building MVC controllers.

Constructor

constructor(
  baseUrl: string,
  token?: string,
  defaultHeaders?: HeadersInit
)

Parameters:

  • baseUrl (required): The base URL for your API (e.g., "https://api.example.com")

  • token (optional): Authentication token that will be included in all requests as Authorization: Bearer <token>. If a custom Authorization header is provided in method calls, it will override this token.

  • defaultHeaders (optional): Default headers that will be included in all requests. Can be:

    • Record<string, string> - Object with header key-value pairs
    • [string, string][] - Array of header tuples
    • Headers - Headers object

    Header Precedence:

    1. Default headers (lowest priority)
    2. Custom headers passed to API methods (override defaults)
    3. Token (overrides default Authorization, but custom Authorization headers take precedence)

Example:

const controller = new BaseController("https://api.example.com", "auth-token", {
  "X-API-Version": "v1",
  "X-Client-ID": "client-123",
});

Protected Properties

  • apiService: ApiService - The API service instance for making HTTP requests
  • apiBasePath: string - The base URL path

Protected Methods

handleError(error: unknown): never

Handles errors and throws appropriate error messages.

try {
  await this.apiService.get("/users");
} catch (error) {
  this.handleError(error); // Throws formatted error
}
getApiUrl(endpoint: string): string

Constructs a full API URL by combining the base path with an endpoint.

const url = this.getApiUrl("/users"); // Returns: "https://api.example.com/users"
createURL(base: string, query: URLSearchParams): string

Creates a URL with query parameters.

const params = new URLSearchParams({ page: "1", limit: "10" });
const url = this.createURL("/users", params); // Returns: "/users?page=1&limit=10"
buildSearchParams<T>(params: T, options?): URLSearchParams

Builds URLSearchParams from an object with advanced options.

const params = this.buildSearchParams(
  { name: "John", age: 30, tags: ["developer", "designer"] },
  {
    rename: { name: "fullName" }, // Rename "name" to "fullName"
    transform: {
      age: (v) => String(v), // Custom transformation
    },
  }
);
// Result: URLSearchParams with fullName=John&age=30&tags=developer,designer

ApiService

The API service provides HTTP methods for making requests. All methods support custom headers that will be merged with default headers and the authentication token.

Header Precedence

When making requests, headers are merged in the following order (higher priority overrides lower):

  1. Default headers (from constructor) - Lowest priority
  2. Custom headers (passed to method) - Override defaults
  3. Token (from constructor) - Overrides default Authorization, but custom Authorization headers take precedence

Example:

const controller = new BaseController(
  "https://api.example.com",
  "token-123",
  { "X-API-Version": "v1" } // Default header
);

// Request includes: Authorization: Bearer token-123, X-API-Version: v1
await controller.apiService.get("/users");

// Custom header overrides default, token still added
await controller.apiService.get("/users", {
  "X-API-Version": "v2", // Overrides default v1
  "X-Request-ID": "123", // New header
});
// Final headers: Authorization: Bearer token-123, X-API-Version: v2, X-Request-ID: 123

// Custom Authorization overrides token
await controller.apiService.get("/users", {
  Authorization: "Bearer custom-token", // Overrides token-123
});
// Final headers: Authorization: Bearer custom-token, X-API-Version: v1

Methods

get<T>(url: string, headers?: HeadersInit, customErrorMessage?: string): Promise<T>

Make a GET request.

Parameters:

  • url (required): The endpoint URL (relative to base URL)
  • headers (optional): Custom headers to include in the request
  • customErrorMessage (optional): Custom error message if request fails

Example:

// Basic request
const users = await this.apiService.get<User[]>("/users");

// With custom headers
const users = await this.apiService.get<User[]>("/users", {
  "X-Request-ID": "123",
  "X-Custom-Header": "value",
});
post<T>(url: string, body?: RequestBody, headers?: HeadersInit, customErrorMessage?: string): Promise<T>

Make a POST request.

Parameters:

  • url (required): The endpoint URL (relative to base URL)
  • body (optional): Request body (object or FormData)
  • headers (optional): Custom headers to include in the request
  • customErrorMessage (optional): Custom error message if request fails

Example:

// Basic POST request
const newUser = await this.apiService.post<User>("/users", {
  name: "John Doe",
  email: "[email protected]",
});

// With custom headers
const newUser = await this.apiService.post<User>(
  "/users",
  { name: "John Doe", email: "[email protected]" },
  { "X-Request-ID": "123" }
);
put<T>(url: string, body?: RequestBody, headers?: HeadersInit, customErrorMessage?: string): Promise<T>

Make a PUT request.

Parameters:

  • url (required): The endpoint URL (relative to base URL)
  • body (optional): Request body (object or FormData)
  • headers (optional): Custom headers to include in the request
  • customErrorMessage (optional): Custom error message if request fails

Example:

const updatedUser = await this.apiService.put<User>(
  "/users/123",
  { name: "Jane Doe" },
  { "X-Request-ID": "123" }
);
patch<T>(url: string, body?: RequestBody, headers?: HeadersInit, customErrorMessage?: string): Promise<T>

Make a PATCH request.

Parameters:

  • url (required): The endpoint URL (relative to base URL)
  • body (optional): Request body (object or FormData)
  • headers (optional): Custom headers to include in the request
  • customErrorMessage (optional): Custom error message if request fails

Example:

const patchedUser = await this.apiService.patch<User>(
  "/users/123",
  { email: "[email protected]" },
  { "X-Request-ID": "123" }
);
delete<T>(url: string, headers?: HeadersInit, customErrorMessage?: string): Promise<T>

Make a DELETE request.

Parameters:

  • url (required): The endpoint URL (relative to base URL)
  • headers (optional): Custom headers to include in the request
  • customErrorMessage (optional): Custom error message if request fails

Example:

// Basic DELETE
await this.apiService.delete("/users/123");

// With custom headers
await this.apiService.delete("/users/123", {
  "X-Request-ID": "123",
});
setToken(token: string | undefined): void

Update the authentication token at runtime.

this.apiService.setToken(newToken);

Advanced Examples

Complete CRUD Controller

import { BaseController } from "mvc-front-sdk";

interface User {
  id: string;
  name: string;
  email: string;
}

interface CreateUserDto {
  name: string;
  email: string;
}

export class UserController extends BaseController {
  constructor() {
    super("https://api.example.com", process.env.API_TOKEN);
  }

  async getAllUsers(filters?: { page?: number; limit?: number }) {
    try {
      const queryParams = this.buildSearchParams(filters);
      const url = this.createURL("/users", queryParams);
      const users = await this.apiService.get<User[]>(url);
      return users;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getUserById(id: string) {
    try {
      const user = await this.apiService.get<User>(`/users/${id}`);
      return user;
    } catch (error) {
      this.handleError(error);
    }
  }

  async createUser(userData: CreateUserDto) {
    try {
      const newUser = await this.apiService.post<User>("/users", userData);
      return newUser;
    } catch (error) {
      this.handleError(error);
    }
  }

  async updateUser(id: string, userData: Partial<User>) {
    try {
      const updatedUser = await this.apiService.put<User>(
        `/users/${id}`,
        userData
      );
      return updatedUser;
    } catch (error) {
      this.handleError(error);
    }
  }

  async deleteUser(id: string) {
    try {
      await this.apiService.delete(`/users/${id}`);
    } catch (error) {
      this.handleError(error);
    }
  }
}

Custom Headers

const data = await this.apiService.post("/users", userData, {
  "X-Custom-Header": "value",
});

FormData Support

const formData = new FormData();
formData.append("file", file);
formData.append("name", "John");

const result = await this.apiService.post("/upload", formData);

Dynamic Token Updates

export class AuthController extends BaseController {
  constructor() {
    super("https://api.example.com");
  }

  async login(credentials: LoginDto) {
    const response = await this.apiService.post<LoginResponse>(
      "/auth/login",
      credentials
    );
    // Update token after successful login
    this.apiService.setToken(response.token);
    return response;
  }

  logout() {
    this.apiService.setToken(undefined);
  }
}

Error Handling

The SDK provides built-in error handling through ApiError:

import { ApiError } from "mvc-front-sdk";

try {
  await this.apiService.get("/users");
} catch (error) {
  if (error instanceof ApiError) {
    console.error(error.statusCode); // HTTP status code
    console.error(error.message); // Error message
    console.error(error.body); // Response body
    console.error(error.isUnAuthenticated()); // Check if 401
  }
  this.handleError(error);
}

TypeScript Support

Full TypeScript support with generics:

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

// Type-safe API calls
const user = await this.apiService.get<User>("/users/123");
// user is typed as User

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Repository

GitHub