This library is an alternative to urlcat
which isn't maintained anymore. That is why it is named after my cat, Luna ๐โโฌ.
Link is because it is easy to say after luna. That's it, that's the whole story.
I (@yoannfleurydev), did not want to fork
urlcat to challenge myself into reimplementing it. Some API design are made to
simplify my development, I hope it will simplify yours too.
Lunalink main purpose is to provide utilities to, given a url, extract parameters and replace them with real values. No more Record<string, string> for your url params!
- ๐ฌ URL Path params type extraction
- ๐ Replace path params placeholders with real values
- ๐ค Tiny (2.8 kB minified + gzipped)
- ๐ฆ TypeScript first
- ๐งช Fully tested
Install the package using your favorite package manager:
npm install @bearstudio/lunalink
yarn add @bearstudio/lunalink
pnpm add @bearstudio/lunalinkNote
These samples of code were purely invented for the example.
Go from this kind of code:
import { useQuery } from '@tanstack/react-query';
import { pick } from 'remeda';
// ๐ You need to maintain path params type as they change in the url,
// no automatic type updates based on the url
type EventCategoryType = {
year: string;
typeId: string;
categoryId: string;
filter?: string;
page?: string;
size?: string;
}
const useEventCategory = (params: EventCategoryType) => {
return useQuery({
queryKey: ['event', 'type', 'category', params],
queryFn: async () => {
// ๐ Here you need to handle searchParams separately
const searchParams = new URLSearchParams(pick(params, ['filter', 'page', 'size']));
// ๐ฉ Tedious url build, hard to read if there is more than two path params
const response = await fetch(`demo/event/${params.year}/type/${params.typeId}/category/${params.categoryId}?${searchParams.toString()}`);
return response.json();
}
})
}to this kind of code:
import { useQuery } from '@tanstack/react-query';
import { type ExtractParams, lunalink } from '@bearstudio/lunalink';
const eventCategoryIdRoute = 'demo/event/:year/type/:typeId/category/:categoryId';
// ๐ Let lunalink detect path params given your url, and add your custom searchParams
// The type will always be up to date with path params declared in your url
type EventCategoryIdRouteType = ExtractParams<typeof eventCategoryIdRoute> & {
filter?: string;
page?: string;
size?: string;
};
const useEventCategory = (params: EventCategoryIdRouteType) => {
return useQuery({
queryKey: ['event', 'type', 'category', params],
queryFn: async () => {
// ๐ Lunalink will replace path params with their values from the params object,
// based on how they are defined in eventCategoryIdRoute.
// And leftovers will be passed as search params.
// No more custom url concatenation!
const response = await fetch(lunalink(eventCategoryIdRoute, params));
return response.json();
},
});
};
useEventCategory({
year: '2025',
typeId: 'event',
categoryId: 'track',
page: '3',
size: '10',
});
// Called url will look like this: 'demo/event/2025/type/event/category/track?page=3&size=10'No more types to maintain, only urls to declare! Easier to read, easier to use ๐
import { lunalink } from '@bearstudio/lunalink';
// lunalink(path, variables, config);
// returns 'a/path/with/a-variable'
lunalink('a/path/with/:variables', { variables: 'a-variable' });
// returns 'a/path/with/a-variable?anotherVar=3'
lunalink('a/path/with/:variables', { variables: 'a-variable', anotherVar: '3' });
// returns 'a/path/my-file.pdf'
lunalink('a/path/:fileName.:ext', { fileName: 'my-file', ext: 'pdf' });
// returns 'https://my.api.com/a/path/with/a-variable'
lunalink(
'a/path/with/:variables',
{ variables: 'a-variable' },
{ baseURL: 'https://my.api.com' }, // trailing slash is not mandatory
);
// You can override the default encodeURIComponent() method used to encode
// path params
// returns 'a/path/with/A-VARIABLE'
lunalink(
'a/path/with/:variables',
{ variables: 'a-variable' },
{
encodeURIComponent: (pathParamValue) => {
return pathParamValue.toLocalUpperCase();
},
},
);This type utility allows you to extract all path params from a url into a Record like object.
Usefull to type function params.
import { ExtractParams } from '@bearstudio/lunalink';
type MyURLPathParams = ExtractParams<'a/path/with/:variable'>;
// type MyURLPathParams = {
// variable: string;
// };
const url = 'a/path/:fileName.:ext'
type AnotherURLPathParams = ExtractParams<typeof url>;
// type AnotherURLPathParams = {
// fileName: string;
// ext: string;
// };join is a utility fonction which allows to join to path. If join detect
a baseURL either in left or right, it will always prepend it to the other param.
If not, the two path will be joined.
import { join } from '@bearstudio/lunalink';
This monorepo includes the following packages/apps:
web: a Next.js app@repo/lunalink: the source code of the library. Learn mode about it in its dedicated README@repo/eslint-config:eslintconfigurations (includeseslint-config-nextandeslint-config-prettier)@repo/typescript-config:tsconfig.jsons used throughout the monorepo
Each package/app is 100% TypeScript.
To build all apps and packages, run the following command:
pnpm build
To develop all apps and packages, run the following command:
pnpm dev
Learn more about the power of Turborepo:
