API Calls
You can find API
endpoints under /src/app/api/route_name/route.ts
We are using react-query
to make NON GET
API
calls to get the maximum performance
benefits read their docs here or continue we have got some examples.
Protecting an API route
if you don't want public access to a specific API
route or you want certain
roles only to be able to access a route you can use the middleware helper
function
- Create a route under
app/api/your_route_name/route.ts
- Import and use the middleware helper function like this
/src/app/api/your_route_name/route.ts
import { withAuthMiddleware } from "lib/auth/middleware";
export const POST = withAuthMiddleware(
async (request, { params }, { session, pathname, user }) => {
// your code here
},
);
- To add only specific allowed roles
/src/app/api/your_route_name/route.ts
import { withAuthMiddleware } from "lib/auth/middleware";
import { Role } from "@prisma/client";
export const POST = withAuthMiddleware(
async (request, { params }, { pathname, session, user }) => {
// your code here
},
[Role.ADMIN],
);
Note
Notice how withAuthMiddleware
gives you access to the current
session
, logged in user
and the pathname
of the route
- To throw or handle an
error
we do something like this
/src/app/api/your_route_name/route.ts
import { withAuthMiddleware } from "lib/auth/middleware";
import { Role } from "@prisma/client";
import { APIError } from "types";
import { getErrorResponse } from "utils/server";
export const POST = withAuthMiddleware(
async (request, { params }, { pathname, session, user }) => {
try {
// your code here
if (expected_fail) {
throw new APIError({
pathname,
message: "your error message",
details: {}, // optional object you might want to send to the client side
status: 400, // optional but it is always recommended to add the correct code
});
}
if (unexpected_fail) {
throw new Error("your error message");
}
} catch (error) {
// Catches the error and logs unexpected errors to console for easy tracking
return getErrorResponse(error, pathname);
}
},
[Role.ADMIN],
);
Note
You throw
Expected fails using APIError
and unexpected fails with
regular Error
as getErrorResponse
will log the unexpected fail to console
so you can see it in your deployment error logs
Calling endpoints
To call these endpoints we are using axios
with interceptors and
configured it to
- Handle errors by showing a
Toast
message - Handle unauthenticated users by showing a log in dialog
- Use the backend base url so we don't have to always add
http://localhost:3000/api
before eachAPI
route path
You can import this configured axios
instance from
TypeScript
import axios from "lib/axios";
In general you can use axios directly anywhere you want but because
we care about performance and clean code we are using react-query
alongside axios
for example if you want to make an api call and follow the same pattern we are following you can do something like this
- Create your api call under
/src/lib/api
/src/lib/api
import axios from "lib/axios";
export const example_api_call = async (payload) => {
return (
axios
// notice we don't write /api at the beginning of the path
// it is already included with our axios instance
.post<ReturnType>("/example/request", payload)
.then((res) => res.data)
);
};
- Use
react-query
'suseMutation
to make use of thisPOST
request
/src/app/example_page/page.tsx
import { example_api_call } from "lib/api";
import { useMutation } from "@tanstack/react-query";
import { Button } from "components/ui/button";
const ExamplePage = () => {
// notice how you get isPending and isError so you don't have
// to create a state for them
const { mutate, isPending, isSuccess, isError } = useMutation({
// pass your api call to mutationFn
mutationFn: example_api_call,
onSuccess: () => {
// do something on success
// e.g. show toast message
// e.g. redirect to a different page
},
onError: () => {
// do something on error
// if you are using our axios instance
// errors from api calls will automatically show
// so do anything other than showing the message
},
});
return (
<div>
<Button
isLoading={isPending}
disabled={isPending}
onClick={() => mutate()}
>
trigger example_api_call()
</Button>
{isSuccess && <p>API Call Successful!</p>}
{isError && <p>API Call Unsuccessful</p>}
</div>
);
};
export default ExamplePage;
Note
You don't have to use our axios instance you can always use an unmodified axios if you need to and you will have to do it in certain situations
import axios from "axios";
Fetch data on the client
If your api call is a GET
request and has to be fetched on the client side
navigate to /src/lib/queries.ts
and update it
/src/lib/queries.ts
import { queryOptions } from "@tanstack/react-query";
import { getMyDataAPICall } from "lib/api";
export const queries = {
// ...
getMyData: queryOptions({
placeholderData: { data: "default value" }, // add default value of the response
queryKey: ["my-data"], // add a key to cache the response (e.g. any keyword that represents your data)
queryFn: getMyDataAPICall, // add your GET request call here
}),
};
Then use it in your page/component like this
/src/app/example_page/page.tsx
"use client";
import { useQuery } from "@tanstack/react-query";
const ExamplePage = () => {
const { data, isFetching } = useQuery(queries.getMyData);
return <div>{isFetching ? "loading" : data}</div>;
};
export default ExamplePage;
Fetching data on the server
If your api call is a GET
request and has to be fetched on the server side
navigate to the page you want to fetch the data /src/app/example_page/page.tsx
/src/app/example_page/page.tsx
import { db } from "lib/db";
// mark your page as async function
const ExamplePage = async () => {
// fetch the data directly from the database
const orders = await db.order.findMany();
// pass the data to any other component
return <MyCustomComponent orders={orders} />;
};
export default ExamplePage;