REST API framework to construct domain API repositories
npm install client-rest-framework

WORK IN PROGRESS
* Introduction
* Installation
* Quick Start
* API
* Serializers
* Repository
* Repository Types
client-rest-framework is a TypeScript library that lets you build repositories to manage entities with just a few lines of code—using patterns familiar to Django REST framework. It ships APIs, serializers, and repositories that make it straightforward to interact with RESTful back‑ends in a structured, type‑safe way.
* Powerful, flexible two‑directional serializers for custom serialisation and deserialisation logic.
* Support for custom serializers so you can tailor mapping to your exact needs.
* Strongly‑typed entities derived from serializers, providing compile‑time safety and avoiding common errors.
* Built‑in support for an Axios‑based HTTP client, with hooks for headers and error handling.
* Object‑oriented design inspired by Django REST framework.
* Composable repositories—include only the operations you need (read‑only, write‑only, or your own mix).
``bash`
npm install client-rest-framework
`ts
import { api, serializers, repositories, pagination } from "client-rest-framework";
// 1. HTTP client
class HTTPClient extends api.AxiosHTTPClient {
getExtraHeaders() {
return { Authorization: Bearer ${access} };
}
// Called when the backend responds with 401
onUnauthenticate = () => {
logout();
};
}
// 2. Base API class
class API
options = { appendSlash: true };
pagination = new pagination.PageNumberPagination
client = new HTTPClient({ baseURL: BASE_URL });
}
// 3. Endpoint‑specific API
class PublicUserAPI extends API
pagination = new pagination.PageNumberPagination
url = "/api/users";
}
// 4. Serializer
class PublicUserSerializer extends serializers.ModelSerializer
id = new serializers.NumberField();
username = new serializers.StringField();
email = new serializers.StringField();
display_name = new serializers.StringField();
date_joined = new serializers.DateField();
}
// 5. Repository
class PublicUserRepository extends repositories.ReadOnlyRepository {
api = new PublicUserAPI();
serializer = new PublicUserSerializer();
}
// 6. Usage
const users = new PublicUserRepository();
const userList = await users.list();
const user = await users.get(1);
`
Create an API class by extending RESTAPI, injecting an HTTPClient, and setting the url field. The API class can then be passed to a repository to realise CRUD operations.
`ts
import { api, pagination } from "client-rest-framework";
import { PublicUserDTO } from "./types";
class HTTPClient extends api.AxiosHTTPClient {
getExtraHeaders() {
return { Authorization: Bearer ${access} };
}
onUnauthenticate = () => {
logout();
};
}
class API
client = new HTTPClient({ baseURL: BASE_URL });
}
class PublicUserAPI extends API
pagination = new pagination.PageNumberPagination
url = "/api/users";
}
`
Two pagination helpers are available:
`ts
// Returns [items: DTO[], { count: number }]
class NumberedAPI extends api.RESTAPI {
pagination = new pagination.PageNumberPagination({
pageSize: 50,
pageSizeQueryParam: "page_size",
pageQueryParam: "page",
});
}
// Returns items untouched
class FlatAPI extends api.RESTAPI {
pagination = new pagination.NoPagination();
}
`
Serializers map data to and from your domain model.
`ts
import { serializers } from "client-rest-framework";
import { PublicUserDTO, RoleKey, UserStatusKey, CategoryKey } from "./types";
export class PublicUserSerializer extends serializers.ModelSerializer
id = new serializers.NumberField({ readonly: true });
username = new serializers.StringField({ readonly: true });
email = new serializers.StringField({ readonly: true });
display_name = new serializers.StringField({ readonly: true });
date_joined = new serializers.DateField({ readonly: true });
notes = new serializers.StringField({ many: true, optional: true });
phone = new serializers.StringField({ optional: true });
roles = new serializers.EnumField
status = new serializers.EnumField
categories = new serializers.EnumField
}
`
`ts
type Domain
type Payload
export type PublicUser = Domain
export type PublicUserPayload = Payload
`
Create bespoke mappings when you need defaults or complex structures:
`ts
export class TemplateSerializer
extends serializers.BaseSerializer
fromDTO = (data: string | null) =>
(typeof data === "string" ? data : DEFAULT_RESUME_TEMPLATE) as ResumeTemplateKey;
toDTO = (data: ResumeTemplateKey) => data;
}
`
`ts
export interface ImageStruct {
file2x : string;
file : string;
thumb : string;
thumb2x: string;
}
export class ImageSerializer
extends serializers.BaseSerializer
private static EXT = ".webp";
fromDTO = (filename: string): ImageStruct => {
const id = filename.split(".")[0];
return {
file2x : ${id}_2x${ImageSerializer.EXT},${id}${ImageSerializer.EXT}
file : ,${id}_t${ImageSerializer.EXT}
thumb : ,${id}_t2x${ImageSerializer.EXT}
thumb2x: ,
};
};
toDTO = (image: ImageStruct | string) =>
typeof image === "string" ? image : image.file;
}
`
`ts
class ResumeMinimalSerializer
extends serializers.ModelSerializer
id = new serializers.StringField({ readonly: true });
updated_at = new serializers.DateField({ readonly: true });
}
export class ApplicationSerializer
extends serializers.ModelSerializer
id = new serializers.NumberField({ readonly: true });
position = new serializers.StringField();
company = new serializers.StringField();
resumes = new ResumeMinimalSerializer({ readonly: true, many: true });
}
`
Define a repository by extending one of the provided classes and wiring in an api and a serializer.
`ts
import { repositories } from "client-rest-framework";
import { PublicUserAPI } from "./api";
import { PublicUserSerializer } from "./serializers";
// Read‑only (get & list)
export class PublicUserRepository extends repositories.ReadOnlyRepository {
api = new PublicUserAPI();
serializer = new PublicUserSerializer();
}
// Full CRUD
export class AdminUserRepository extends repositories.ModelRepository {
api = new PublicUserAPI();
serializer = new PublicUserSerializer();
}
`
`ts
const users = new PublicUserRepository();
// Get a user
const user = await users.get(1);
// List users (page 1)
const [list, { count }] = await users.list(1);
// Full CRUD example
const admin = new AdminUserRepository();
const created = await admin.create(data);
const updated = await admin.update(1, diff);
await admin.delete(1);
`
`ts`
const [articles] = await articlesRepository.list(1, {
queryParams: {
page_size : 20,
author : 42,
rating_min: 5,
},
});
Choose a predefined repository class—or compose your own—to control allowed operations.
| Repository | Methods | Typical Use |
| ------------------------ | --------------------------------- | ------------------------------ |
| ModelRepository | create, get, list, update, delete | Full admin control |ReadOnlyRepository
| | get, list | Public APIs, documentation |ListOnlyRepository
| | list | Dropdowns, catalogues, search |CreateOnlyRepository
| | create | Logging, analytics, write‑only |RetrieveOnlyRepository
| | get | Detail pages |
`ts
import { repositories } from "client-rest-framework";
class BulkCleanupRepository extends repositories.mixins.DestroyMixin(
repositories.mixins.ListMixin(repositories.BaseRepository),
) {
api = new CleanupAPI();
serializer = new ItemSerializer();
}
`
* list()` currently supports DRF PageNumberPagination only.
* The package is in early alpha—APIs may change.