Building your application

API routes

Edit this page

While Server Functions can be a good way to write server-side code for data needed by your UI, sometimes you need to expose API routes. Some reasons for wanting API Routes include:

  • There are additional clients that want to share this logic.
  • Exposing a GraphQL or tRPC endpoint.
  • Exposing a public facing REST API.
  • Writing webhooks or auth callback handlers for OAuth.
  • Having URLs not serving HTML, but other kinds of documents like PDFs or images.

For these use cases, SolidStart provides a way to write these routes in a way that is easy to understand and maintain. API routes are just similar to other routes and follow the same filename conventions as UI Routes.

The difference between API routes and UI routes is in what you should export from the file. UI routes export a default Solid component, while API Routes do not. Rather, they export functions that are named after the HTTP method that they handle.


Writing an API route

To write an API route, you can create a file in a directory. While you can name this directory anything, it is common to name it api to indicate that the routes in this directory are for handling API requests:

routes/api/test.ts
export function GET() {
// ...
}
export function POST() {
// ...
}
export function PATCH() {
// ...
}
export function DELETE() {
// ...
}

API routes get passed an APIEvent object as their first argument. This object contains:

  • request: Request object representing the request sent by the client.
  • params: Object that contains the dynamic route parameters. For example, if the route is /api/users/:id, and the request is made to /api/users/123, then params will be { id: 123 }.
  • fetch: An internal fetch function that can be used to make requests to other API routes without worrying about the origin of the URL.

An API route is expected to return JSON or a Response object. In order to handle all methods, you can define a handler function that binds multiple methods to it:

routes/api/all.ts
async function handler() {
// ...
}
export const GET = handler;
export const POST = handler;
// ...

An example of an API route that returns products from a certain category and brand is shown below:

routes/api/product/[category]/[brand].ts
import type { APIEvent } from "@solidjs/start/server";
import store from "./store";
export async function GET({ params }: APIEvent) {
console.log(`Category: ${params.category}, Brand: ${params.brand}`);
const products = await store.getProducts(params.category, params.brand);
return products;
}

Session management

Since HTTP is a stateless protocol, you need to manage the state of the session on the server. For example, if you want to know who the user is, the most secure way of doing this is through the use of HTTP-only cookies. Cookies are a way to store data in the user's browser that persist in the browser between requests.

The user's request is exposed through the Request object. Through parsing the Cookie header, the cookies can be accessed and any helpers from vinxi/http can be used to make that a bit easier.

import type { APIEvent } from "@solidjs/start/server";
import { getCookie } from "vinxi/http";
import store from "./store";
export async function GET(event: APIEvent) {
const userId = getCookie("userId");
if (!userId) {
return new Response("Not logged in", { status: 401 });
}
const user = await store.getUser(event.params.userId);
if (user.id !== userId) {
return new Response("Not authorized", { status: 403 });
}
return user;
}

In this example, you can see that the userId is read from the cookie and then used to look up the user in the store. For more information on how to use cookies for secure session management, read the session documentation.


Exposing a GraphQL API

SolidStart makes it easy to implement a GraphQL API. GraphQL is a query language for APIs and a runtime for executing those queries by using a type system you define for your data.

To implement a GraphQL API, you need to define a schema and resolvers. The graphql function takes a GraphQL schema and returns a function that can be used as an API route handler.

First, to implement a GraphQL API, install the graphql library. Following that, you can implement your schema and resolvers in a file and then export a handler function that will be used as the API route:

routes/graphql.ts
import { buildSchema, graphql } from "graphql";
import type { APIEvent } from "@solidjs/start/server";
// Define GraphQL Schema
const schema = buildSchema(`
type Message {
message: String
}
type Query {
hello(input: String): Message
goodbye: String
}
`);
// Define GraphQL Resolvers
const rootValue = {
hello: () => {
return {
message: "Hello World"
};
},
goodbye: () => {
return "Goodbye";
}
};
// request handler
const handler = async (event: APIEvent) => {
// get request body
const body = await new Response(event.request.body).json();
// pass query and save results
const result = await graphql({ rootValue, schema, source: body.query });
// send query result
return result;
};
export const GET = handler;
export const POST = handler;

Exposing a tRPC server route

tRPC is a modern TypeScript-first API framework that is designed to be easy to use and understand.

To expose a tRPC server route, you need to write your router. Once you have written your router, you can put it in a separate file so that you can export the type for your client.

lib/router.ts
import { initTRPC } from "@trpc/server";
import { wrap } from "@decs/typeschema";
import { string } from "valibot";
const t = initTRPC.create();
export const appRouter = t.router({
hello: t.procedure.input(wrap(string())).query(({ input }) => {
return `hello ${input ?? "world"}`;
})
});
export type AppRouter = typeof appRouter;

An example of a simple client that you can use to fetch data from your tRPC server is shown below:

lib/trpc.ts
import { createTRPCProxyClient, httpBatchLink, loggerLink } from "@trpc/client";
import type { AppRouter } from "./router";
export const client = createTRPCProxyClient<AppRouter>({
links: [loggerLink(), httpBatchLink({ url: "http://localhost:3000/api/trpc" })]
});

Finally, you can use the fetch adapter to write an API route that acts as the tRPC server.

routes/api/trpc/[trpc].ts
import { type APIEvent } from "solid-start/api";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { appRouter } from "~/lib/router";
const handler = (event: APIEvent) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req: event.request,
router: appRouter,
createContext: () => ({})
});
export const GET = handler;
export const POST = handler;

To learn more about tRPC, you can read the tRPC documentation.

Report an issue with this page