@aibulat/restclients/dummyjson
dummyjson.com — the closest thing to a modern jsonplaceholder: same "nothing is persisted" contract, but with richer entities, real list and search params, and a working JWT flow. No key needed.
import {DummyJsonApi} from '@aibulat/restclients/dummyjson';
const api = new DummyJsonApi();
const res = await api.getProducts({limit: 10, select: ['title', 'price']});
const page = await res.json();
console.log(page.products, page.total);The list envelope
Every collection response names its array after the resource:
{"products": [...], "total": 194, "skip": 0, "limit": 10}which is what DummyPage<K, T> encodes — getProducts() resolves to something with a products key, getUsers() to one with users. A products envelope will not typecheck as a users one.
type DummyPage<K extends string, T> =
{total: number, skip: number, limit: number} & {[P in K]: Array<T>};All eight collections take the same options:
interface DummyListOptions {
limit?: number,
skip?: number,
select?: string | Array<string>, // arrays are comma-joined
sortBy?: string,
order?: 'asc' | 'desc'
}Methods
Products
getProducts(options?, config?)
getProduct(id, config?)
searchProducts(q, options?, config?)
getProductCategories(config?) // full objects
getProductCategoryList(config?) // bare slugs
getProductsByCategory(slug, options?, config?)
addProduct(item, config?) // POST /products/add
updateProduct(id, item, config?) // PUT
patchProduct(id, item, config?) // PATCH
deleteProduct(id, config?)Writes are simulated: the response echoes what you sent with an id attached, and deleteProduct returns the product plus {isDeleted, deletedOn}.
Users, posts, comments
getUsers(options?, config?) getUser(id, config?) searchUsers(q, options?, config?)
getUserCarts(userId, config?) getUserPosts(userId, options?, config?) getUserTodos(userId, options?, config?)
getPosts(options?, config?) getPost(id, config?) searchPosts(q, options?, config?)
getPostsByTag(tag, options?, config?) getPostComments(postId, options?, config?)
getComments(options?, config?) getComment(id, config?)Todos, carts, quotes, recipes
getTodos(options?, config?) getTodo(id, config?) getRandomTodo(config?)
getCarts(options?, config?) getCart(id, config?)
getQuotes(options?, config?) getQuote(id, config?) getRandomQuote(config?)
getRecipes(options?, config?) getRecipe(id, config?) searchRecipes(q, options?, config?)
getRecipesByTag(tag, options?, config?) getRecipesByMealType(type, options?, config?)Auth
login mints a short-lived access token; me() is the only call that requires one. Credentials are any user's username and password from /users — the documented demo pair is emilys / emilyspass.
const api = new DummyJsonApi();
const login = await api.login({username: 'emilys', password: 'emilyspass'});
api.setToken((await login.json()).accessToken);
const me = await api.me();setToken() exists because the token does not exist until login has already returned, so it cannot be a constructor-only option. Calling it with no argument clears the header again. If you already have a token, pass it straight in:
new DummyJsonApi({token: 'access-token'});login(credentials, config?) // -> AuthUser (the user plus both tokens)
me(config?) // -> DummyUser; needs a bearer token
refresh(refreshToken, expiresInMins?, config?) // -> AuthTokens
setToken(token?) // sync, not a requestexpiresInMins (default 60) is accepted by both login and refresh.
Types
Product, DummyUser, DummyPost, DummyComment, DummyTodo, Cart, Quote, Recipe, Category, plus the nested Dimensions, Review, ProductMeta, DummyAddress, DummyCompany, Reactions, CartProduct.
Auth: LoginRequest, AuthTokens, AuthUser. Writes: NewProduct (a partial product that must have a title), ProductUpdate, Deleted. Errors: DummyError, which is {message: string}.
Only documented fields are typed. Several endpoints return extra keys that upstream adds and removes without notice, so the entities describe what you can rely on rather than a full snapshot.