While we think that using createServerData$ is the best way to write server-side code for data needed by your UI, sometimes you need to expose API routes. Reasons for wanting API Routes include:
You have additional clients that want to share this logic.
You want to expose a GraphQL or tRPC endpoint.
You want to expose a public facing REST API.
You need to write webhooks or auth callback handlers for OAuth.
You want to have URLs not serving HTML, but other kinds of documents like PDFs or images.
SolidStart makes it easy to write routes for these use cases.
Writing an API Route
API routes are just like any other route and follow the same filename conventions as UI Routes. The only difference is in what you should export from the file. API Routes do not export a default Solid component and a routeData function.
Instead, they export functions that are named after the HTTP method that they handle. For example, a GET request would be handled by the exported GET function. If a handler is not defined for a given HTTP method, SolidStart will return a 405 Method Not Allowed response.
routes/api/students.ts
tsx
// handles HTTP GET requests to /api/students
exportfunctionGET
function GET(): Response
() {
returnnewResponse
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Hello World");
}
exportfunctionPOST
function POST(): void
() {
// ...
}
exportfunctionPATCH
function PATCH(): void
() {
// ...
}
exportfunctionDELETE
function DELETE(): void
() {
// ...
}
routes/api/students.ts
tsx
// handles HTTP GET requests to /api/students
exportfunctionGET
function GET(): Response
() {
returnnewResponse
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Hello World");
}
exportfunctionPOST
function POST(): void
() {
// ...
}
exportfunctionPATCH
function PATCH(): void
() {
// ...
}
exportfunctionDELETE
function DELETE(): void
() {
// ...
}
These functions can also sit in your UI routes beside your component. They can handle non-GET HTTP requests for those routes.
An API route gets passed an APIEvent object as its first argument. This object contains:
request: Request object representing the request sent by the client.
params: Object that contains the dynamic route parameters, e.g. for /api/students/:id, when user requests /api/students/123 , params.id will be "123".
env: Environment context, environment specific settings, and bindings.
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 a Response object. Let's look at an example of an API route that returns a list of students in a given house, in a specific year:
routes/api/[house]/students/year-[year].ts
tsx
import { typeAPIEvent
(alias) interface APIEvent
import APIEvent
, json
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start/api";
importhogwarts
import hogwarts
from"./hogwarts";
exportasyncfunctionGET
function GET({ params }: APIEvent): Promise<Response>
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start/api";
importhogwarts
import hogwarts
from"./hogwarts";
exportasyncfunctionGET
function GET({ params }: APIEvent): Promise<Response>
As HTTP is a stateless protocol, for awesome dynamic experiences, you want to know the state of the session on the client. For example, you want to know who the user is. The secure way of doing this is to use HTTP-only cookies.
You can store session data in them and they are persisted by the browser that your user is using. We expose the Request object which represents the user's request. The cookies can be accessed by parsing the Cookie header.
Let's look at an example of how to use the cookie to identify the user:
routes/api/[house]/admin.ts
tsx
import { typeAPIEvent
(alias) interface APIEvent
import APIEvent
, json
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start/api";
import { parseCookie
Parse a cookie header.
Parse the given cookie header string into an object
The object has the various cookies as keys(names) => values
(alias) function parseCookie(str: string, options?: CookieParseOptions): Record<string, string>
import parseCookie
} from"solid-start";
importhogwarts
import hogwarts
from"./hogwarts";
exportasyncfunctionGET
function GET({ request, params }: APIEvent): Promise<Response>
({ request
(parameter) request: Request
, params
(parameter) params: {
[key: string]: string;
}
}:APIEvent
(alias) interface APIEvent
import APIEvent
) {
constcookie
const cookie: Record<string, string>
=parseCookie
Parse a cookie header.
Parse the given cookie header string into an object
The object has the various cookies as keys(names) => values
(alias) parseCookie(str: string, options?: CookieParseOptions | undefined): Record<string, string>
import parseCookie
(request
(parameter) request: Request
.headers
Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get
(method) Headers.get(name: string): string | null
("Cookie") ??"");
constuserId
const userId: string
=cookie
const cookie: Record<string, string>
['userId'];
if (!userId
const userId: string
) {
returnnewResponse
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Not logged in", { status
(property) ResponseInit.status?: number | undefined
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Not authorized", { status
(property) ResponseInit.status?: number | undefined
: 403 });
}
returnjson
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) json<{
students: {
name: string;
house: string;
year: string;
}[];
}>(data: {
students: {
name: string;
house: string;
year: string;
}[];
}, init?: number | ResponseInit): Response
import json
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start/api";
import { parseCookie
Parse a cookie header.
Parse the given cookie header string into an object
The object has the various cookies as keys(names) => values
(alias) function parseCookie(str: string, options?: CookieParseOptions): Record<string, string>
import parseCookie
} from"solid-start";
importhogwarts
import hogwarts
from"./hogwarts";
exportasyncfunctionGET
function GET({ request, params }: APIEvent): Promise<Response>
({ request
(parameter) request: Request
, params
(parameter) params: {
[key: string]: string;
}
}:APIEvent
(alias) interface APIEvent
import APIEvent
) {
constcookie
const cookie: Record<string, string>
=parseCookie
Parse a cookie header.
Parse the given cookie header string into an object
The object has the various cookies as keys(names) => values
(alias) parseCookie(str: string, options?: CookieParseOptions | undefined): Record<string, string>
import parseCookie
(request
(parameter) request: Request
.headers
Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get
(method) Headers.get(name: string): string | null
("Cookie") ??"");
constuserId
const userId: string
=cookie
const cookie: Record<string, string>
['userId'];
if (!userId
const userId: string
) {
returnnewResponse
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Not logged in", { status
(property) ResponseInit.status?: number | undefined
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
("Not authorized", { status
(property) ResponseInit.status?: number | undefined
: 403 });
}
returnjson
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) json<{
students: {
name: string;
house: string;
year: string;
}[];
}>(data: {
students: {
name: string;
house: string;
year: string;
}[];
}, init?: number | ResponseInit): Response
import json
This is a very simple example and quite unsecure, but you can see how you can use cookies to read and store session data. Read the session documentation for more information on how to use cookies for more secure session management.
You can read more about using HTTP cookies in the MDN documentation.
Exposing a GraphQL API
SolidStart makes it easy to implement a GraphQL API. The graphql function takes a GraphQL schema and returns a function that can be used as an API route handler.
Install the graphql library
Then in any route file you can implement a graphql api like below
routes/graphql.ts
ts
import { buildSchema
A helper function to build a GraphQLSchema directly from a source
document.
(alias) function buildSchema(source: string | Source, options?: BuildSchemaOptions & ParseOptions): GraphQLSchema
import buildSchema
, graphql
(alias) function graphql(args: GraphQLArgs): Promise<ExecutionResult>
import graphql
} from"graphql";
import { typeAPIEvent
(alias) interface APIEvent
import APIEvent
, json
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start";
// Define GraphQL Schema
constschema
const schema: GraphQLSchema
=buildSchema
A helper function to build a GraphQLSchema directly from a source
document.
(alias) buildSchema(source: string | Source, options?: (BuildSchemaOptions & ParseOptions) | undefined): GraphQLSchema
import buildSchema
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) json<ExecutionResult<ObjMap<unknown>, ObjMap<unknown>>>(data: ExecutionResult<ObjMap<unknown>, ObjMap<unknown>>, init?: number | ResponseInit): Response
import json
A helper function to build a GraphQLSchema directly from a source
document.
(alias) function buildSchema(source: string | Source, options?: BuildSchemaOptions & ParseOptions): GraphQLSchema
import buildSchema
, graphql
(alias) function graphql(args: GraphQLArgs): Promise<ExecutionResult>
import graphql
} from"graphql";
import { typeAPIEvent
(alias) interface APIEvent
import APIEvent
, json
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) function json<Data>(data: Data, init?: number | ResponseInit): Response
import json
} from"solid-start";
// Define GraphQL Schema
constschema
const schema: GraphQLSchema
=buildSchema
A helper function to build a GraphQLSchema directly from a source
document.
(alias) buildSchema(source: string | Source, options?: (BuildSchemaOptions & ParseOptions) | undefined): GraphQLSchema
import buildSchema
This Fetch API interface represents the response to a request.
var Response: new (body?: BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
A JSON response. Converts `data` to JSON and sets the `Content-Type` header.
(alias) json<ExecutionResult<ObjMap<unknown>, ObjMap<unknown>>>(data: ExecutionResult<ObjMap<unknown>, ObjMap<unknown>>, init?: number | ResponseInit): Response
import json
Here is a simple client that you can use in your routeData function to fetch data from your tRPC server. You can also use the proxy in createServerData$ and createServerAction$ functions, but it's usually better to just use it in a createResource or createRouteData function.