Resource Routes in Generated Data Loaders
Resource Routes Overview
Resource routes are special routes that require resolved data rather than deferred promises. They are commonly used for API endpoints, data fetching routes, and other scenarios where immediate data resolution is required.
Configuration
routes.json Configuration
{
"/api/teams": {
"isResourceRoute": true,
"resourceUri": "teams",
"queries": {
"GetTeamsDocument": "{orgName: params.orgName}"
},
"middlewares": [
"@your-package/middleware/configurations"
]
}
}
GraphQL Query Definition
query GetTeams($orgName: String!) {
teams(orgName: $orgName) {
id
name
members {
id
name
role
}
settings {
isPublic
allowInvites
}
}
}
Generated Loader
import { json } from '@remix-run/node';
import { GetTeamsDocument } from './graphql/teams.generated';
export const loader = async ({ context, params }) => {
const loaderErrors = [];
let defaultLoaderData = {};
// Configuration middleware
const middlewareStack = [];
const { apolloClient: client } = context;
try {
// Execute queries with immediate resolution
const teamsResult = await client.query({
query: GetTeamsDocument,
variables: { orgName: params.orgName },
fetchPolicy: 'network-only',
});
defaultLoaderData = {
teams: teamsResult.data.teams
};
// Execute middleware if configured
if (options.resourceUri) {
const { configurations } = await executeConfigurationMiddleware(params);
defaultLoaderData.configurations = configurations;
}
return json({
...defaultLoaderData,
errors: loaderErrors
});
} catch (error) {
loaderErrors.push(error);
return json({ errors: loaderErrors }, { status: 500 });
}
};
Example Usage
Resource Route Component
// app/routes/api/teams.ts
import { useLoaderData } from '@remix-run/react';
import type { Team } from '~/types';
interface LoaderData {
teams: Team[];
configurations?: {
maxTeamSize: number;
features: string[];
};
errors?: Array<{ message: string }>;
}
export default function TeamsResource() {
const { teams, configurations, errors } = useLoaderData<LoaderData>();
if (errors?.length) {
return json({
error: 'Failed to load teams data',
details: errors
}, {
status: 500
});
}
return json({
teams,
settings: configurations,
timestamp: new Date().toISOString()
});
}
Consuming Component
// app/routes/teams/index.tsx
import { useEffect, useState } from 'react';
export default function TeamsPage() {
const [teams, setTeams] = useState<Team[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchTeams() {
try {
const response = await fetch('/api/teams?orgName=acme');
const data = await response.json();
setTeams(data.teams);
} catch (error) {
console.error('Failed to fetch teams:', error);
} finally {
setLoading(false);
}
}
fetchTeams();
}, []);
if (loading) return <div>Loading teams...</div>;
return (
<div>
<h1>Teams</h1>
<div className="teams-grid">
{teams.map(team => (
<TeamCard
key={team.id}
team={team}
maxSize={data.configurations?.maxTeamSize}
/>
))}
</div>
</div>
);
}
// TeamCard component
const TeamCard = ({ team, maxSize }) => (
<div className="team-card">
<h3>{team.name}</h3>
<div className="member-count">
Members: {team.members.length} / {maxSize}
</div>
<div className="member-list">
{team.members.map(member => (
<div key={member.id} className="member">
{member.name} ({member.role})
</div>
))}
</div>
<div className="team-settings">
{team.settings.isPublic ? 'Public' : 'Private'} Team
{team.settings.allowInvites && ' • Invites Enabled'}
</div>
</div>
);
Using with Forms
// app/routes/teams/new.tsx
import { Form, useActionData } from '@remix-run/react';
export default function NewTeam() {
const actionData = useActionData();
return (
<Form method="post" action="/api/teams">
<div>
<label htmlFor="teamName">Team Name:</label>
<input
id="teamName"
name="teamName"
type="text"
required
/>
</div>
<div>
<label htmlFor="isPublic">
<input
id="isPublic"
name="isPublic"
type="checkbox"
/>
Make team public
</label>
</div>
<button type="submit">Create Team</button>
{actionData?.errors && (
<div className="error">
{actionData.errors.map(error => (
<p key={error.message}>{error.message}</p>
))}
</div>
)}
</Form>
);
}
Key Differences from Regular Routes
Data Resolution
- All data is resolved before sending response
- Always returns
json
responses - No streaming or deferred loading
Error Handling
- Errors are collected and returned immediately
- Appropriate HTTP status codes are set
- Error details can be included in the response
Middleware Integration
- Middleware executes synchronously
- All configurations are resolved before response
- Results are included in the JSON response
Response Format
- Structured JSON responses
- Can include metadata and configurations
- Suitable for API consumption
Back to Index | Previous: Query Parameters Generator | Next: CSS Import and Stylesheets