httpc is a javascript/typescript framework for building function-based API with minimal code and end-to-end type safety.
You just write functions and export them. No need to worry how the server will execute them.
function add(a: number, b: number) {
return a + b;
}
function greet(name: string) {
return `Hello ${name}`;
}
export default {
add,
greet,
}
From the client you can call server functions like normal javascript functions with a natural syntax.
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"
httpc is an abstraction over the standard HTTP protocol. With httpc you can build an API that speaks functions, arguments and return values, not http verbs, headers, resource paths, data serialization…
The httpc framework hides all the complexity of the underling HTTP while keeping you focused on what matters: the function logic.
Run common logic via middlewares.
import { httpCall } from "@httpc/server";
const getPostById = httpCall(
Authenticated(), // <-- authentication check
Validate(String), // <-- parameters validation
Cache("5m"), // <-- result caching
async (postId: string) => {
const post = await db.select("posts").where("id", postId);
if (!post) {
throw new NotFoundError();
}
return post;
}
);
Access the request context from everywhere in your application. Be in a handler, middleware o service logic, the context is always available with no need to pass parameters around.
async function getPosts() {
const { user } = useContext();
let category = "news";
if (user) {
category = user.preferredCategory;
trace("Getting user preferred posts");
}
return await db.select("posts").where("category", category);
}
function trace(message: string) {
const { requestId } = useContext();
console.log(`[req:${requestId}] ${message}`);
}
Hooks encapsulate common logic around the request context. By convention hooks adopt the use
prefix.
async function addNewComment(postId: string, message: string) {
const user = useUser();
if (!useIsAuthorized("comment:create")) {
throw new ForbiddenError("Cannot add comments");
}
return await db.createComment({
userId: user.id,
postId,
message
});
}
@httpc/kit offers several builtin hooks to cache data, to perform authorization checks, to make transactions…
You can host a full httpc API inside a serverless environment like Vercel, AWS Lambda or Netlify functions. This gives the advantage to deploy a single serverless function handling the whole API.
For example with Vercel, you can expose all your API functions:
//file: api/index.ts
import { createHttpCVercelAdapter } from "@httpc/adapter-vercel";
import calls from "../calls";
export default createHttpCVercelAdapter({
calls,
log: "info"
});
Then, you can call API functions from pages with full type checking:
//file: pages/home.tsx
import { createClient, ClientDef } from "@httpc/client";
import { useQuery, useMutation } from "react-query";
import type calls from "../calls"; // <-- import calls definition
// create a typed client
const client = createClient<ClientDef<typeof calls>>();
export default function Home() {
const posts = useQuery(["posts"], () => client.posts.getLatest());
return (
<div class="container">
{posts.data.map(post =>
<div class="post">
<h2>{post.title}</h2>
<p>{post.text}</p>
</div>
)}
</div>
);
}
Customize builtin objects to fit your needs, while keeping autocompletion and type checking working.
You can extend the request context:
/// <reference types="@httpc/kit/env" />
interface IHttpCContext {
// example custom property
environment: string
// other custom properties here
// ...
}
There're many entities available to extend. For example you can redefine the user object with custom properties:
interface IUser {
firstName: string
lastName: string
}