Just call a function()
Build function-based API with minimal code and end-to-end type safety
For javascript/typescript
Build function-based API with minimal code and end-to-end type safety
For javascript/typescript
server.ts
function add(x: number, y: number) {
return x + y;
};
function greet(name: string) {
return `Hello ${name}`;
};
export default {
add,
greet
}
client.ts
import createClient from "@your-package/api-client";
const client = createClient({
endpoint: "http://api.domain.com"
});
let result = await client.add(1, 2);
// result: 3
let message = await client.greet("Edith");
// message: "Hello Edith"
server.ts
import { httpCall, useUser, Authenticated, Cached } from "@httpc/kit";
import db from "./db";
const getProfile = httpCall(
Authenticated(),
Cached("1h"),
async () => {
const user = useUser();
return await db.select("profiles")
.where("userId", user.id);
}
)
export default {
getProfile
}
client.ts
import createClient, { AuthHeader } from "@your-package/api-client";
const client = createClient({
endpoint: "http://api.domain.com",
middleware: [
AuthHeader("Bearer", localStorage.getItem("access-token"))
]
});
const profile = await client.getProfile();
server.ts
import { httpCall, Authenticated, Validate, useUser, useInjected, Optional, NotFoundError } from "@httpc/server";
import { TicketService } from "./services";
import { REASONS } from "./data";
const closeTicket = httpCall(
Authenticated("role:admin"),
Validate(
String,
Optional(reason => REASONS.include(reason))
),
async (ticketId: string, reason?: "resolved" | "rejected") => {
const user = useUser();
const tickets = useInjected(TicketService);
const ticket = await tickets.get(ticketId);
if (!ticket) {
throw new NotFoundError();
}
return await tickets.close(ticketId, {
by: user.id,
reason: reason || "resolved"
});
}
);
export default {
tickets: {
close: closeTicket,
query: // ...
}
}
client.ts
import createClient, { AuthHeader } from "@your-service/api-client";
const client = createClient({
endpoint: "https://api.your-service.com",
middlewares: [
AuthHeader("Bearer", localStorage.getItem("accessToken"))
]
});
const tickets = await client.tickets.query({ assignedTo: "me" });
let firstTicket = tickets[0];
if (firstTicket) {
firstTicket = await client.tickets.close(firstTicket.id, "resolved");
}
server.ts
import { httpCall, useUser, useTransaction } from "@httpc/kit";
import { OrderService, Inventory } from "./services";
import { OrderCreateSchema } from "./data";
const createOrder = httpCall(
Authenticated(),
Validate(OrderCreateSchema),
async (orderData: OrderCreateSchema) => {
const user = useUser();
const order = useTransaction(OrderService, Inventory,
async (orders, inventory) => {
await inventory.reserve(order.product.id, order.product.quantity);
return await orders.create({
userId: user.id,
productId: order.product.id,
quantity: order.product.quantity,
});
}
);
return order;
}
);
export default {
orders: {
create: createOrder
}
}
client.ts
import createClient, { AuthHeader } from "@your-service/api-client";
const client = createClient({
endpoint: "https://api.your-service.com",
middlewares: [
AuthHeader("Bearer", localStorage.getItem("accessToken"))
]
});
const offers = await client.offers.getLatest();
const order = await client.orders.create({
product: {
id: offers.product.id,
quantity: 1
}
});
middlewares.ts
import { useContext, useContextProperty, PassThroughMiddleware } from "@httpc/kit";
import { sessionManager } from "./services";
export function SessionMiddleware() {
return PassThroughMiddleware(async () => {
const { user } = useContext();
if (!user) {
return;
}
const session = await sessionManager.retrieve(user.id);
useContextProperty("sessionId", session.id);
useContextProperty("changes", session.changes);
});
}
server.ts
import { httpCall, httpGroup, useUser, useContextProperty, Authenticated } from "@httpc/kit";
import { SessionMiddleware } from "./middlewares";
import { sessionManager, editor } from "./services";
const commit = httpCall(async () => {
const user = useUser();
const changes = useContextProperty("changes");
if (changes) {
await editor.persist(changes);
useContextProperty("changes", []);
}
await sessionManager.clear(user.id);
});
export default httpGroup(
Authenticated(),
SessionMiddleware(),
{
edit,
undo,
commit,
}
)
client.ts
import createClient, { QueryParam } from "@your-package/api-client";
const client = createClient({
endpoint: "http://api.domain.com",
middleware: [
QueryParam("api-key", process.env.CLIENT_API_KEY)
]
});
await client.createEditSession();
const change1 = await client.edit("#object-id", {
color: "red"
});
const change2 = await client.edit("#object-id", {
fontSize: "16px"
});
await client.undo(change2.id);
await client.commit();
calls.ts
import { httpCall, Validate } from "@httpc/kit";
import db from "./db";
async function getLatest() {
return db.select("posts").take(10)
.orderBy("created_at", "desc");
}
const addLike = httpCall(
Validate(String),
async (postId: string) => {
return db.update("posts").where("id", postId)
.increase("likes", 1);
}
);
export default {
posts: {
getLatest,
addLike,
}
}
api/index.ts
import { createHttpCVercelAdapter } from "@httpc/adapter-vercel";
import calls from "../calls";
export default createHttpCVercelAdapter({
calls,
log: "info"
});
pages/home.tsx
import { createClient, ClientDef } from "@httpc/client";
import { useQuery, useMutation } from "react-query";
import type calls from "../calls";
const client = createClient<ClientDef<typeof calls>>();
export default function Home() {
const posts = useQuery(["posts"], () => client.posts.getLatest());
const addLike = useMutation((postId: string) => client.posts.addLike(postId), {
onSuccess: () => queryClient.invalidateQueries(["posts"])
});
return (
<div class="container">
{posts.data.map(post =>
<div class="post">
<h2>{post.title}</h2>
<button onClick={() => addLike.mutate(post.id)}>Like</button>
</div>
)}
</div>
);
}
Don't worry about raw HTTP. No path binding, no http verbs, no headers, no data serialization.
With httpc you deal just with functions, arguments and return values.
See how to design a function-based APIHandle requests with idiomatic code with no complex wiring and unnatural syntax.
Less code, translates in less effort to produce, evolve, test, refactor and maintain.
See how you reduce your code with httpcBreak things when you like to happen. No more missing parameter, type mismatch...
Type definitions provide autocompletion and keep your more productive avoiding frequent doc lookups.
Learn more about e2e typesafetyUse builtin components with sensible defaults for common concerns like authentication, authorization, logging...
@httpc/kit provides a toolbox with many utilities that cover lots of use cases out of the box.
Rich modular architecture, you can plug into anything and model any behavior.
You can go beyond httpc and manage form submissions, web hooks, rest requests...
You can generate and publish a client to provide a ready-to-go experience for 3rd parties.
With a standard package you can easily share the client, manage versions and backward compatibility.
Meet the tools making a function-based API an easy task
@httpc/server
The httpc core component allowing function calls over the standard http protocol
@httpc/client
Typed interface used by consumers to interact safely with functions exposed by an httpc server
@httpc/kit
Rich toolbox of builtin components to manage common use cases and business concerns
@httpc/cli
Commands to setup a project, generate clients, manage versioning and help with common tasks