Elysia Tools

Explora herramientas, ejemplos, explicaciones visuales y guías temáticas en un solo espacio claro.

Categorías populares

Temas

Idioma
ZHESPTRUFRDEEN

Elysia Tools

Navegación

Frameworks de API

Ejemplos de tRPC

Ejemplos de tRPC incluyendo APIs seguras de extremo a extremo, procedimientos, middleware, autenticación y patrones de integración cliente-servidor

Ejemplos

Entradas de esta colección

CódigotypescriptSimple

Configuración Básica de tRPC

Configuración básica de servidor y cliente tRPC con procedimientos simples y seguridad de tipos

Dificultad

4/10

Tiempo estimado

30 min

Etiquetas

trpc, typescript, api, type-safe

Requisitos previos

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;
CódigotypescriptIntermedio

Middleware y Autenticación tRPC

Patrones avanzados de tRPC incluyendo middleware, autenticación, autorización y manejo de errores

Dificultad

7/10

Tiempo estimado

45 min

Etiquetas

trpc, middleware, auth, security, typescript

Requisitos previos

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;
CódigotypescriptIntermedio

Integración tRPC React

Integración de tRPC con React incluyendo hooks, queries, mutations, suscripciones e integración con TanStack Query

Dificultad

8/10

Tiempo estimado

50 min

Etiquetas

trpc, react, frontend, integration, typescript

Requisitos previos

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;

Herramientas

Herramientas que suelen usarse con este ejemplo

Relacionado

Colecciones de ejemplos relacionadas

Guía de uso de la herramienta

Contexto para entender esta colección de ejemplos

Título

Ejemplos de tRPC

Descripción

Ejemplos de tRPC incluyendo APIs seguras de extremo a extremo, procedimientos, middleware, autenticación y patrones de integración cliente-servidor

Resumen

Ejemplos de tRPC incluyendo APIs seguras de extremo a extremo, procedimientos, middleware, autenticación y patrones de integración cliente-servidor

Cuándo usarlo

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

Cómo funciona

  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.

Casos de uso

  • Configuración Básica de tRPC
  • Middleware y Autenticación tRPC
  • Integración tRPC React

Preguntas frecuentes

What is included in Ejemplos de tRPC?

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

Inicio
Herramientas3197
Ejemplos593
Visualizaciones461
Visores de archivos103
Temas229
Etiquetas333
Imágenes, audio y video655

Herramientas de procesamiento, conversión y optimización de imágenes, audio y video

Matemáticas, fechas y finanzas644

Calculadoras, herramientas numéricas, lógica de fechas, estadística y finanzas

Diseño y color296

Herramientas de color, maquetación, gráficos, estilo visual y apoyo al diseño

Texto y redacción209

Herramientas de redacción, limpieza, formato, extracción y análisis de texto

Desarrollo y Web193

Utilidades para desarrolladores, red, depuración web y automatización

Conversión y codificación175

Herramientas de conversión de formatos, archivos, unidades, archivos comprimidos y codificación

Estructurar una captura de interfaz, revisarla y entregarla a desarrollo
Boceto de piano, revisión MIDI y entrega de una maqueta de escucha
Revisión de texturas retro y entrega de recursos
Revisión y entrega de guías de procedimiento con capturas
Revisión visual de un rediseño adaptable y entrega de evidencias

© 2026 Elysia Tools. Todos los derechos reservados.

DocumentaciónPreciosAcerca deTérminos de servicioPolítica de privacidadCookiesContacto

Development

JSON a TypeScript

Convierte datos JSON en interfaces de TypeScript o alias de tipo, con tipos unión, campos opcionales e inferencia de objetos anidados

Frameworks web

Ejemplos de Elysia.js

Ejemplos del framework web TypeScript Elysia.js incluyendo Hello World, ruteo, validación y desarrollo de plugins

Frameworks web

Ejemplos de NestJS

Ejemplos del framework enterprise Node.js NestJS incluyendo módulos, controladores, servicios y despliegue