Hey 👋, @yme/api
is a package that defines the type-safe API routes. No server required and zero dependencies.
If you are developing a full-stack web application, you should take a look tRPC.
- I am a frond-end developer.
[module].[action]
is good for me. (e.g.post.create(input)
)- I usually use axios but there may be others. (e.g. fetch)
- It may be need some transformer for each api response. because my colleagues are very unrestrained
So I create this package to solve these problems. (I hope so)
🚧 TODOs
- with react-query
- generate from api docs e.g. swagger
pnpm add @yme/api
import { createApi, createRouter } from '@yme/api';
import axios from 'axios';
import { z } from 'zod';
const request = axios.create({}).request;
const router = createRouter();
const createUserSchema = z.object({
username: z.string().min(1),
password: z.string().min(1),
age: z.number().optional(),
});
type CreateUserInput = z.infer<typeof createUserSchema>;
interface UserType {
id: number;
username: string;
}
const routes = {
users: {
create: router
.post('/users') // api.user.create({...}) => POST /users
.validator(createUserSchema) // (input: CreateUserInput) => any
.T<UserType>()
.selector((user) => user.id), // (user: UserType) => number
delete: router
.delete('/users/:id') // api.user.delete(123) replace path with input => DELETE /users/123
.validator(z.number())
.T<void>(), // (input: number) => void
one: router
.get('/users/:id') // api.user.one(123)
.T<UserType>(), // (input: number | {id: number}) => UserType (input type is number | string or {id: number | string} from path params)
update: router
.put('/users/:id') // api.user.update(input) will replace with input[id] => PUT /users/{id}
.validator(
z.object({
username: z.string().optional(),
age: z.number().optional(),
id: z.number(),
}),
)
.T<UserType>(), // (input: {username?: string; age?: number; id: number}) => UserType
list: router
.get('/users') // api.user.list({page: 1}) => GET /users?page=1
.T<{ page: number }, { list: UserType[] }>()
.validator(({ page }) => (page > 0 ? { page } : { page: 1 })) // (input: {page: number}) => {page: number}
.selector(({ list }) => list), // (listResult: {list: UserType[]}) => UserType[]
},
};
const api = createApi({
http: request,
routes,
});
declare module '@yme/api' {
interface Register {
api: typeof api;
}
}
// use api
const userId = await api.users.create({ username: 'yoyo', password: 'yoyo123' });
console.log(`user id: ${userId}`);
// delete user
await api.users.delete(userId);
// done.
How to define a route?
// method and path always required
router.post('/users');
// method, path and validator (input)
router.post('/users').validator(schema);
// with selector
router.post('/users').T<UserType>().selector((user) => user.id);
router.post('/users').selector((user: UserType) => user.id);
// method, path, validator (input) and selector (output)
router.post('/users').validator(schema).selector((user: UserType) => user.id);
// with types (input, output)
router.post('/users').T<In, Out>();
// validator and output
router.post('/users').validator(schema).T<Out>();
// types first
router.post('/users').T<In, Out>().validator((in) => in).selector(out => out);
Create a api resource (v1.2.0)
const r = createResource('/users');
const user = {
// GET /users
list: r.get().T<{records: UserType[]; total: number}>(),
// GET /users/:id
one: r.get('/:id'),
// POST /users
create: r.post().validator(userSchema).selector((user: UserType) => user.id),
// PUT /users/:id (/:id by defaults)
update: r.put().validator(z.object({name: z.string(); id: z.string()})).T<UserType>(),
// DELETE /users/:id
delete: r.delete(),
// POST /users/:id/reset-password
resetPassword: r.post('/:id/reset-password').validator(z.object({password: z.string()})).T<boolean>(),
}
const api = createApi({routes: { user }});
MIT