Elysia Tools

Parcourez outils, exemples, explications visuelles et guides thématiques dans un espace clair.

Catégories populaires

Dossiers

Langue
ZHESPTRUFRDEEN

Elysia Tools

Navigation mobile

Frameworks API

Exemples tRPC

Exemples tRPC incluant les API de type sécurisé de bout en bout, procédures, middleware, authentification et patrons d'intégration client-serveur

Exemples

Entrées de cette collection

Code sourcetypescriptSimple

Configuration de Base tRPC

Configuration de base serveur et client tRPC avec procédures simples et sécurité de type

Difficulté

4/10

Temps estimé

30 min

Étiquettes

trpc, typescript, api, type-safe

Prérequis

TypeScript basics, Express.js, REST API concepts

typescript
// tRPC Basic Setup Example
// Server-side setup and client integration

import { initTRPC } from '@trpc/server';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { observable } from '@trpc/server/observable';
import express from 'express';
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';

// 1. Initialize tRPC
const t = initTRPC.create();

// 2. Define context type
export type Context = {
  userId?: string;
  user?: {
    id: string;
    name: string;
  };
};

// 3. Create router
export const appRouter = t.router({
  // Simple query procedure
  hello: t.procedure
    .query(() => {
      return { greeting: 'Hello tRPC!' };
    }),

  // Query with input validation
  getUser: t.procedure
    .input((val: unknown) => {
      if (typeof val === 'string' || typeof val === 'number') {
        return String(val);
      }
      throw new Error(`Invalid input: expected string or number`);
    })
    .query(({ input }) => {
      return {
        id: input,
        name: `User ${input}`,
        email: `user${input}@example.com`,
        createdAt: new Date().toISOString()
      };
    }),

  // Simple mutation
  createUser: t.procedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected object');
      }
      const { name, email } = val as { name?: unknown; email?: unknown };

      if (typeof name !== 'string' || typeof email !== 'string') {
        throw new Error('Name and email must be strings');
      }

      return { name, email };
    })
    .mutation(({ input }) => {
      // Simulate database save
      const user = {
        id: Math.random().toString(36).substr(2, 9),
        name: input.name,
        email: input.email,
        createdAt: new Date().toISOString()
      };

      return { success: true, user };
    }),

  // Query with context
  me: t.procedure
    .query(({ ctx }) => {
      return ctx.user || null;
    }),

  // Subscription example
  onHello: t.procedure
    .subscription(() => {
      return observable((emit) => {
        const timer = setInterval(() => {
          emit.next({ greeting: 'Hello from subscription!' });
        }, 1000);

        return () => {
          clearInterval(timer);
        };
      });
    }),
});

// Export router type
export type AppRouter = typeof appRouter;

// 4. Express server setup
const app = express();

app.use('/trpc', createExpressMiddleware({
  router: appRouter,
  createContext: ({ req }) => {
    // Simulate authentication
    const userId = req.headers['x-user-id'] as string;

    return {
      userId,
      user: userId ? {
        id: userId,
        name: `User ${userId}`
      } : undefined,
    };
  },
}));

const server = app.listen(3000, () => {
  console.log('tRPC server running on port 3000');
});

// 5. Client setup (can be in separate file)
const trpc = createTRPCProxyClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
      headers: {
        'x-user-id': 'user123', // Example auth header
      },
    }),
  ],
});

// 6. Client usage examples
async function clientExamples() {
  // Query
  const hello = await trpc.hello.query();
  console.log(hello); // { greeting: 'Hello tRPC!' }

  // Query with input
  const user = await trpc.getUser.query('123');
  console.log(user); // { id: '123', name: 'User 123', email: '[email protected]', ... }

  // Mutation
  const newUser = await trpc.createUser.mutate({
    name: 'John Doe',
    email: '[email protected]'
  });
  console.log(newUser); // { success: true, user: { id: 'abc', name: 'John Doe', ... } }

  // Query with context
  const me = await trpc.me.query();
  console.log(me); // { id: 'user123', name: 'User user123' }

  // Subscription
  const subscription = trpc.onHello.subscribe(undefined, {
    onData(data) {
      console.log(data); // { greeting: 'Hello from subscription!' } every second
    },
    onError(err) {
      console.error('Subscription error:', err);
    },
  });

  // Clean up subscription after 5 seconds
  setTimeout(() => {
    subscription.unsubscribe();
  }, 5000);
}

// Execute client examples
clientExamples().catch(console.error);

// 7. Error handling example
const errorRouter = t.router({
  mightFail: t.procedure
    .query(() => {
      // Simulate potential error
      if (Math.random() > 0.5) {
        throw new Error('Random failure occurred!');
      }
      return { success: true };
    }),
});

// 8. Combined router with error handling
export const combinedRouter = t.router({
  app: appRouter,
  error: errorRouter,
});

export type CombinedRouter = typeof combinedRouter;

// 9. Async procedure example
const asyncRouter = t.router({
  fetchData: t.procedure
    .input((val: unknown) => {
      if (typeof val !== 'string') {
        throw new Error('Expected string URL');
      }
      return val;
    })
    .query(async ({ input }) => {
      // Simulate async operation (e.g., database query or API call)
      await new Promise(resolve => setTimeout(resolve, 1000));

      return {
        url: input,
        data: `Data fetched from ${input}`,
        timestamp: new Date().toISOString()
      };
    }),

  processItem: t.procedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected object');
      }
      return val as { id: string; data: any };
    })
    .mutation(async ({ input }) => {
      // Simulate async processing
      await new Promise(resolve => setTimeout(resolve, 500));

      return {
        processed: true,
        id: input.id,
        result: `Processed: ${JSON.stringify(input.data)}`,
        processedAt: new Date().toISOString()
      };
    }),
});

export type AsyncRouter = typeof asyncRouter;
Code sourcetypescriptIntermédiaire

Middleware et Authentification tRPC

Patrons avancés tRPC incluant middleware, authentification, autorisation et gestion d'erreurs

Difficulté

7/10

Temps estimé

45 min

Étiquettes

trpc, middleware, auth, security, typescript

Prérequis

tRPC basics, TypeScript, JWT, Express.js, Security concepts

typescript
// tRPC Middleware and Authentication Example

import { initTRPC, TRPCError } from '@trpc/server';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import express from 'express';
import jwt from 'jsonwebtoken';

// 1. Initialize tRPC
const t = initTRPC.create();

// 2. Define database/user types
interface User {
  id: string;
  email: string;
  name: string;
  role: 'admin' | 'user';
}

interface Database {
  users: User[];
  posts: Array<{
    id: string;
    title: string;
    content: string;
    authorId: string;
    published: boolean;
    createdAt: Date;
  }>;
}

// Mock database
const db: Database = {
  users: [
    { id: '1', email: '[email protected]', name: 'Admin User', role: 'admin' },
    { id: '2', email: '[email protected]', name: 'Regular User', role: 'user' },
  ],
  posts: [
    { id: '1', title: 'First Post', content: 'Hello World', authorId: '1', published: true, createdAt: new Date() },
    { id: '2', title: 'Draft Post', content: 'This is a draft', authorId: '2', published: false, createdAt: new Date() },
  ],
};

// 3. Define context type
export interface Context {
  user?: User;
  req: express.Request;
}

// 4. Authentication middleware
const isAuthed = t.middleware(async ({ next, ctx }) => {
  const authHeader = ctx.req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET || 'secret-key') as { userId: string };
    const user = db.users.find(u => u.id === decoded.userId);

    if (!user) {
      throw new TRPCError({ code: 'UNAUTHORIZED', message: 'User not found' });
    }

    return next({
      ctx: {
        ...ctx,
        user,
      },
    });
  } catch (error) {
    throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Invalid token' });
  }
});

// 5. Role-based authorization middleware
const hasRole = (role: 'admin' | 'user') =>
  t.middleware(async ({ next, ctx }) => {
    if (!ctx.user) {
      throw new TRPCError({ code: 'UNAUTHORIZED' });
    }

    if (ctx.user.role !== role && ctx.user.role !== 'admin') {
      throw new TRPCError({
        code: 'FORBIDDEN',
        message: `Requires ${role} role or higher`
      });
    }

    return next({
      ctx: {
        ...ctx,
      },
    });
  });

// 6. Logging middleware
const logger = t.middleware(async ({ next, path, type, input }) => {
  const start = Date.now();
  const result = await next({ ctx: {} });
  const duration = Date.now() - start;

  console.log(`[${new Date().toISOString()}] ${type.toUpperCase()} ${path}: ${duration}ms`);

  return result;
});

// 7. Error handling middleware
const errorHandler = t.middleware(async ({ next }) => {
  try {
    return await next();
  } catch (error) {
    console.error('tRPC Error:', error);
    throw error;
  }
});

// 8. Rate limiting middleware
const rateLimit = t.middleware(async ({ next, ctx }) => {
  const clientIP = ctx.req.ip;
  const currentTime = Date.now();

  // In production, you'd use Redis or a proper rate limiting store
  const requestCounts = new Map<string, { count: number; resetTime: number }>();

  const existing = requestCounts.get(clientIP);

  if (existing && currentTime < existing.resetTime) {
    if (existing.count >= 10) { // 10 requests per minute
      throw new TRPCError({
        code: 'TOO_MANY_REQUESTS',
        message: 'Rate limit exceeded. Please try again later.'
      });
    }
    existing.count++;
  } else {
    requestCounts.set(clientIP, {
      count: 1,
      resetTime: currentTime + 60000, // 1 minute
    });
  }

  return next();
});

// 9. Protected procedure creator
const protectedProcedure = t.procedure
  .use(errorHandler)
  .use(logger)
  .use(rateLimit)
  .use(isAuthed);

const adminProcedure = protectedProcedure.use(hasRole('admin'));

// 10. Create router with middleware
export const authRouter = t.router({
  // Public procedures
  login: t.procedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected credentials object');
      }
      return val as { email: string; password: string };
    })
    .mutation(({ input }) => {
      // Simulate authentication
      const user = db.users.find(u => u.email === input.email);

      if (!user || input.password !== 'password') {
        throw new TRPCError({
          code: 'UNAUTHORIZED',
          message: 'Invalid credentials'
        });
      }

      const token = jwt.sign(
        { userId: user.id },
        process.env.JWT_SECRET || 'secret-key',
        { expiresIn: '24h' }
      );

      return { token, user };
    }),

  // Protected procedures
  getProfile: protectedProcedure
    .query(({ ctx }) => {
      return ctx.user;
    }),

  updateProfile: protectedProcedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected profile data');
      }
      return val as { name?: string; email?: string };
    })
    .mutation(({ input, ctx }) => {
      if (!ctx.user) {
        throw new TRPCError({ code: 'UNAUTHORIZED' });
      }

      const userIndex = db.users.findIndex(u => u.id === ctx.user!.id);
      if (userIndex === -1) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
      }

      // Update user
      const updatedUser = { ...db.users[userIndex] };
      if (input.name) updatedUser.name = input.name;
      if (input.email) updatedUser.email = input.email;

      db.users[userIndex] = updatedUser;

      return updatedUser;
    }),

  // Admin-only procedures
  getUsers: adminProcedure
    .query(() => {
      return db.users;
    }),

  deleteUser: adminProcedure
    .input((val: unknown) => {
      if (typeof val !== 'string') {
        throw new Error('Expected user ID string');
      }
      return val;
    })
    .mutation(({ input }) => {
      const userIndex = db.users.findIndex(u => u.id === input);
      if (userIndex === -1) {
        throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
      }

      const deletedUser = db.users[userIndex];
      db.users.splice(userIndex, 1);

      return { success: true, deletedUser };
    }),

  // Post management with ownership validation
  createPost: protectedProcedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected post data');
      }
      const { title, content } = val as { title?: unknown; content?: unknown };

      if (typeof title !== 'string' || typeof content !== 'string') {
        throw new Error('Title and content must be strings');
      }

      return { title, content };
    })
    .mutation(({ input, ctx }) => {
      const post = {
        id: Math.random().toString(36).substr(2, 9),
        title: input.title,
        content: input.content,
        authorId: ctx.user!.id,
        published: false,
        createdAt: new Date(),
      };

      db.posts.push(post);
      return post;
    }),

  // Procedure with multiple middleware
  getSensitiveData: protectedProcedure
    .use(logger)
    .use(rateLimit)
    .query(({ ctx }) => {
      // This procedure uses both auth middleware and additional middleware
      return {
        user: ctx.user,
        data: 'This is sensitive data only accessible to authenticated users',
        timestamp: new Date().toISOString(),
      };
    }),

  // Custom error handling
  riskyOperation: protectedProcedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected config object');
      }
      return val as { shouldFail: boolean };
    })
    .mutation(({ input }) => {
      if (input.shouldFail) {
        throw new TRPCError({
          code: 'INTERNAL_SERVER_ERROR',
          message: 'The operation failed as requested',
          cause: new Error('Simulated failure')
        });
      }

      return { success: true, message: 'Operation completed successfully' };
    }),
});

// 11. Validation middleware example
const validateInput = <T>(validator: (input: unknown) => T) =>
  t.middleware(async ({ next, rawInput }) => {
    if (!rawInput) {
      throw new TRPCError({ code: 'BAD_REQUEST', message: 'Input is required' });
    }

    try {
      const parsed = validator(rawInput);
      return next({ ctx: { parsedInput: parsed } });
    } catch (error) {
      throw new TRPCError({
        code: 'BAD_REQUEST',
        message: `Validation error: ${error.message}`
      });
    }
  });

// Example with validation middleware
const validatedRouter = t.router({
  createUserWithValidation: t.procedure
    .input((val: unknown) => {
      if (typeof val !== 'object' || val === null) {
        throw new Error('Expected user object');
      }
      const user = val as { name: unknown; email: unknown; age: unknown };

      if (typeof user.name !== 'string' || user.name.length < 2) {
        throw new Error('Name must be at least 2 characters');
      }

      if (typeof user.email !== 'string' || !user.email.includes('@')) {
        throw new Error('Valid email is required');
      }

      if (typeof user.age !== 'number' || user.age < 18) {
        throw new Error('Age must be at least 18');
      }

      return user as { name: string; email: string; age: number };
    })
    .use(validateInput((input: unknown) => {
      // Additional validation logic here
      return input as { name: string; email: string; age: number };
    }))
    .mutation(({ ctx }) => {
      const { parsedInput } = ctx as { parsedInput: { name: string; email: string; age: number } };

      const newUser: User = {
        id: Math.random().toString(36).substr(2, 9),
        email: parsedInput.email,
        name: parsedInput.name,
        role: 'user'
      };

      db.users.push(newUser);
      return { success: true, user: newUser };
    }),
});

// 12. Combined router
export const combinedRouter = t.router({
  auth: authRouter,
  validated: validatedRouter,
});

export type AppRouter = typeof combinedRouter;

// 13. Express server setup with middleware
const app = express();
app.use(express.json());

app.use('/trpc', createExpressMiddleware({
  router: combinedRouter,
  createContext: ({ req }) => ({ req }),
  onError: ({ error, type, path, input, ctx, req }) => {
    // Global error handling
    console.error(`tRPC Error on ${type.toUpperCase()} ${path}:`, error);

    if (error.code === 'INTERNAL_SERVER_ERROR') {
      // Log internal errors
      console.error('Internal error details:', error.cause);
    }
  },
}));

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`tRPC server with middleware running on port ${PORT}`);
});

export default app;
Code sourcetypescriptIntermédiaire

Intégration tRPC React

Intégration tRPC avec React incluant hooks, queries, mutations, suscriptions et intégration TanStack Query

Difficulté

8/10

Temps estimé

50 min

Étiquettes

trpc, react, frontend, integration, typescript

Prérequis

tRPC basics, React hooks, TanStack Query, TypeScript

typescript
// tRPC React Integration Example
// Frontend React integration with tRPC and TanStack Query

import React, { useState } from 'react';
import { createTRPCReact } from '@trpc/react-query';
import { httpBatchLink } from '@trpc/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

// 1. tRPC React Query setup
export const trpc = createTRPCReact<AppRouter>();

// 2. Create tRPC client
const getTRPCClient = () => {
  return trpc.createClient({
    links: [
      httpBatchLink({
        url: 'http://localhost:3000/trpc',
        headers: () => {
          const token = localStorage.getItem('auth-token');
          return token ? { Authorization: `Bearer ${token}` } : {};
        },
      }),
    ],
  });
};

// 3. Query Client setup
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
      retry: (failureCount, error) => {
        // Don't retry on 4xx errors
        if (error.data?.code === 'UNAUTHORIZED' || error.data?.code === 'FORBIDDEN') {
          return false;
        }
        return failureCount < 3;
      },
    },
  },
});

// 4. App wrapper component
export function App() {
  const [trpcClient] = useState(() => getTRPCClient());

  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <div className="app">
          <Navigation />
          <Routes />
        </div>
      </QueryClientProvider>
    </trpc.Provider>
  );
}

// 5. Navigation component
function Navigation() {
  const utils = trpc.useContext();

  const handleLogout = () => {
    localStorage.removeItem('auth-token');
    // Invalidate all queries when logging out
    utils.auth.getProfile.invalidate();
  };

  return (
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/profile">Profile</a></li>
        <li><a href="/posts">Posts</a></li>
      </ul>
      <button onClick={handleLogout}>Logout</button>
    </nav>
  );
}

// 6. Authentication component
function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const login = trpc.auth.login.useMutation({
    onSuccess: (data) => {
      localStorage.setItem('auth-token', data.token);
      // Invalidate and refetch user profile
      trpc.auth.getProfile.invalidate();
    },
    onError: (error) => {
      alert(`Login failed: ${error.message}`);
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    login.mutate({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Login</h2>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>
      <div>
        <label>Password:</label>
        <input
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
      </div>
      <button type="submit" disabled={login.isLoading}>
        {login.isLoading ? 'Logging in...' : 'Login'}
      </button>
      {login.error && <p className="error">{login.error.message}</p>}
    </form>
  );
}

// 7. Profile component with tRPC hooks
function Profile() {
  const { data: user, isLoading, error } = trpc.auth.getProfile.useQuery();
  const utils = trpc.useContext();

  const [isEditing, setIsEditing] = useState(false);
  const [editName, setEditName] = useState(user?.name || '');

  const updateProfile = trpc.auth.updateProfile.useMutation({
    onSuccess: () => {
      setIsEditing(false);
      // Invalidate to refetch updated data
      utils.auth.getProfile.invalidate();
    },
  });

  const handleSave = () => {
    updateProfile.mutate({ name: editName });
  };

  if (isLoading) return <div>Loading profile...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>Please log in to view your profile</div>;

  return (
    <div className="profile">
      <h2>Profile</h2>
      <div>
        <strong>Email:</strong> {user.email}
      </div>
      <div>
        <strong>Role:</strong> {user.role}
      </div>
      <div>
        <strong>Name:</strong>
        {isEditing ? (
          <>
            <input
              type="text"
              value={editName}
              onChange={(e) => setEditName(e.target.value)}
            />
            <button onClick={handleSave} disabled={updateProfile.isLoading}>
              {updateProfile.isLoading ? 'Saving...' : 'Save'}
            </button>
            <button onClick={() => setIsEditing(false)}>Cancel</button>
          </>
        ) : (
          <>
            {user.name}
            <button onClick={() => {
              setEditName(user.name);
              setIsEditing(true);
            }}>
              Edit
            </button>
          </>
        )}
      </div>
      {updateProfile.error && (
        <p className="error">{updateProfile.error.message}</p>
      )}
    </div>
  );
}

// 8. Posts management component
function PostsManagement() {
  const { data: posts, isLoading } = trpc.auth.createPost.useQuery(undefined, {
    // Custom query key to fetch posts
    queryKey: ['posts'],
    // This is a workaround since we don't have a dedicated getPosts procedure
  });

  const createPost = trpc.auth.createPost.useMutation();
  const utils = trpc.useContext();

  const [newPost, setNewPost] = useState({ title: '', content: '' });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    createPost.mutate(newPost, {
      onSuccess: () => {
        setNewPost({ title: '', content: '' });
        // Invalidate posts list
        utils.auth.createPost.invalidate();
      },
    });
  };

  if (isLoading) return <div>Loading posts...</div>;

  return (
    <div className="posts">
      <h2>Create Post</h2>
      <form onSubmit={handleSubmit}>
        <div>
          <input
            type="text"
            placeholder="Title"
            value={newPost.title}
            onChange={(e) => setNewPost({ ...newPost, title: e.target.value })}
            required
          />
        </div>
        <div>
          <textarea
            placeholder="Content"
            value={newPost.content}
            onChange={(e) => setNewPost({ ...newPost, content: e.target.value })}
            required
          />
        </div>
        <button type="submit" disabled={createPost.isLoading}>
          {createPost.isLoading ? 'Creating...' : 'Create Post'}
        </button>
        {createPost.error && (
          <p className="error">{createPost.error.message}</p>
        )}
      </form>

      <h3>Posts</h3>
      {posts && (
        <div className="posts-list">
          {/* Render posts here */}
          <p>Posts would be rendered here with actual data</p>
        </div>
      )}
    </div>
  );
}

// 9. Data fetching with custom hooks
function useUserData() {
  const { data: user, isLoading: userLoading } = trpc.auth.getProfile.useQuery();
  const { data: posts, isLoading: postsLoading } = trpc.auth.createPost.useQuery(undefined, {
    queryKey: ['user-posts'],
  });

  return {
    user,
    posts,
    isLoading: userLoading || postsLoading,
  };
}

function UserDashboard() {
  const { user, posts, isLoading } = useUserData();

  if (isLoading) return <div>Loading dashboard...</div>;
  if (!user) return <div>Please log in</div>;

  return (
    <div className="dashboard">
      <h2>Welcome, {user.name}!</h2>
      <p>You have {posts?.length || 0} posts</p>

      <div className="stats">
        <div className="stat-card">
          <h3>{user.role === 'admin' ? 'Admin Dashboard' : 'User Dashboard'}</h3>
          <p>Role: {user.role}</p>
        </div>
      </div>
    </div>
  );
}

// 10. Optimistic updates example
function TodoList() {
  const { data: todos = [] } = trpc.todos.list.useQuery();
  const utils = trpc.useContext();

  const addTodo = trpc.todos.add.useMutation({
    onMutate: async (newTodo) => {
      // Cancel any outgoing refetches
      await utils.todos.list.cancel();

      // Snapshot the previous value
      const previousTodos = utils.todos.list.getData();

      // Optimistically update to the new value
      utils.todos.list.setData(undefined, (old) => [
        ...(old || []),
        { ...newTodo, id: 'temp-' + Date.now(), completed: false }
      ]);

      // Return context with the previous value
      return { previousTodos };
    },
    onError: (err, newTodo, context) => {
      // If the mutation fails, use the context returned from onMutate
      utils.todos.list.setData(undefined, context?.previousTodos);
    },
    onSettled: () => {
      // Always refetch after error or success
      utils.todos.list.invalidate();
    },
  });

  return (
    <div>
      <h2>Todos</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <button
        onClick={() => addTodo.mutate({ title: 'New Todo' })}
        disabled={addTodo.isLoading}
      >
        Add Todo
      </button>
    </div>
  );
}

// 11. Subscription component
function RealTimeData() {
  const { data, error, isLoading } = trpc.onHello.useSubscription(undefined, {
    onData(data) {
      console.log('Received data:', data);
    },
    onError(err) {
      console.error('Subscription error:', err);
    },
  });

  if (isLoading) return <div>Connecting to real-time updates...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Real-time Updates</h2>
      <p>Latest message: {data?.greeting}</p>
    </div>
  );
}

// 12. Error boundary for tRPC errors
class TrpcErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean; error?: Error }
> {
  constructor(props: { children: React.ReactNode }) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('tRPC Error caught by boundary:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: undefined })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// 13. Main app routes
function Routes() {
  const [route, setRoute] = useState('/');

  const renderRoute = () => {
    switch (route) {
      case '/':
        return <UserDashboard />;
      case '/profile':
        return <Profile />;
      case '/posts':
        return <PostsManagement />;
      case '/todos':
        return <TodoList />;
      case '/realtime':
        return <RealTimeData />;
      default:
        return <LoginForm />;
    }
  };

  return (
    <TrpcErrorBoundary>
      <div>
        <nav style={{ marginBottom: '20px' }}>
          <button onClick={() => setRoute('/')}>Dashboard</button>
          <button onClick={() => setRoute('/profile')}>Profile</button>
          <button onClick={() => setRoute('/posts')}>Posts</button>
          <button onClick={() => setRoute('/todos')}>Todos</button>
          <button onClick={() => setRoute('/realtime')}>Real-time</button>
        </nav>
        {renderRoute()}
      </div>
    </TrpcErrorBoundary>
  );
}

// 14. Custom hook for offline support
function useOfflineSupport() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  React.useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
}

// Usage example
function AppWithOfflineSupport() {
  const isOnline = useOfflineSupport();

  return (
    <div className={isOnline ? 'online' : 'offline'}>
      {!isOnline && <div className="offline-warning">You are offline</div>}
      <App />
    </div>
  );
}

export default AppWithOfflineSupport;

Outils

Outils souvent utilisés avec cet exemple

Associé

Collections d’exemples associées

Guide d'utilisation de l'outil

Contexte pour comprendre cette collection d’exemples

Titre

Exemples tRPC

Description

Exemples tRPC incluant les API de type sécurisé de bout en bout, procédures, middleware, authentification et patrons d'intégration client-serveur

Aperçu

Exemples tRPC incluant les API de type sécurisé de bout en bout, procédures, middleware, authentification et patrons d'intégration client-serveur

Quand l’utiliser

  • Use this sample set when you want reference inputs for Exemples tRPC.
  • Use it to preview output formats and compare related sample variants.

Fonctionnement

  1. 1Open a sample entry to inspect the prepared example payload or asset.
  2. 2Compare it with related tools to understand how the workflow connects.

Cas d’usage

  • Configuration de Base tRPC
  • Middleware et Authentification tRPC
  • Intégration tRPC React

FAQ

What is included in Exemples tRPC?

This page includes curated sample entries, related tools, and neighboring sample collections for Exemples tRPC.

Accueil
Outils3197
Exemples593
Visualisations461
Visionneuses de fichiers103
Dossiers229
Étiquettes333
Images, audio et vidéo655

Outils de traitement, conversion et optimisation d’images, d’audio et de vidéo

Maths, dates et finance644

Calculateurs, outils numériques, logique de dates, statistiques et finance

Design et couleur296

Outils de couleur, mise en page, graphisme, style visuel et assistance design

Texte et rédaction209

Outils de rédaction, nettoyage, formatage, extraction et analyse de texte

Développement et Web193

Utilitaires développeur, réseau, débogage Web et assistants d’automatisation

Conversion et encodage175

Outils de conversion de formats, fichiers, archives, unités et encodages

Structurer une capture d'interface, la relire et la transmettre au développement
Esquisse au piano, contrôle MIDI et livraison d'une maquette d'écoute
Revue de textures rétro et remise des ressources
Revue et remise d’un guide opératoire illustré par captures
Revue visuelle d’une refonte responsive et remise des preuves

© 2026 Elysia Tools. Tous droits réservés.

DocumentationTarifsÀ proposConditions d'utilisationPolitique de confidentialitéCookiesContact

Development

JSON vers TypeScript

Convertit des données JSON en interfaces TypeScript ou alias de type, avec types union, champs facultatifs et inférence d'objets imbriqués

Frameworks web

Exemples Elysia.js

Exemples du framework web moderne Elysia.js TypeScript incluant Hello World, routage, validation et développement de plugins

Frameworks web

Exemples NestJS

Exemples du framework enterprise Node.js NestJS incluant modules, contrôleurs, services et déploiement