TypeScript y Desarrollo Server-First: Arquitecturas Modernas para Backend en 2025
Hola HaWkers, ¿ya percibiste cómo TypeScript transformó completamente la forma de desarrollar backend en los últimos años?
En 2025, TypeScript no es apenas "JavaScript con tipos" — es la base de arquitecturas server-first que combinan seguridad de tipos end-to-end, performance superior, y experiencia de desarrollo inigualable.
Vamos a explorar patrones modernos de desarrollo backend con TypeScript que están dominando la industria.
El Paradigma Server-First con TypeScript
El desarrollo server-first prioriza la lógica en el servidor, reduciendo JavaScript enviado al cliente. TypeScript es perfecto para esto:
// Arquitectura Server-First con tRPC
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
// Schema de validación con Zod
const userSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user', 'moderator'])
});
type User = z.infer<typeof userSchema>;
// Router type-safe
const appRouter = t.router({
users: t.router({
list: t.procedure
.query(async () => {
const users = await db.users.findMany();
return users;
}),
getById: t.procedure
.input(z.string().uuid())
.query(async ({ input: userId }) => {
const user = await db.users.findUnique({
where: { id: userId }
});
if (!user) {
throw new Error('User not found');
}
return user;
}),
create: t.procedure
.input(userSchema.omit({ id: true }))
.mutation(async ({ input }) => {
const user = await db.users.create({
data: {
...input,
id: crypto.randomUUID()
}
});
return user;
}),
update: t.procedure
.input(z.object({
id: z.string().uuid(),
data: userSchema.partial().omit({ id: true })
}))
.mutation(async ({ input }) => {
const user = await db.users.update({
where: { id: input.id },
data: input.data
});
return user;
})
})
});
export type AppRouter = typeof appRouter;
Type-Safety End-to-End
La magia de TypeScript en server-first es compartir tipos entre frontend y backend:
// Cliente tRPC con inferencia automática de tipos
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
const client = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc'
})
]
});
// Uso con type-safety completo
async function loadUsers() {
// TypeScript sabe exactamente el tipo de retorno
const users = await client.users.list.query();
// Autocomplete funciona perfectamente
users.forEach(user => {
console.log(user.name); // ✅ TypeScript sabe que existe
console.log(user.invalid); // ❌ Error en tiempo de desarrollo
});
}
async function createUser() {
// Validación en tiempo de desarrollo
const newUser = await client.users.create.mutate({
name: 'Juan García',
email: 'juan@example.com',
role: 'user' // ✅ Solo 'admin' | 'user' | 'moderator'
});
return newUser;
}Patrones Modernos de Backend TypeScript
1. Repository Pattern con Prisma
// Base repository genérico
import { PrismaClient } from '@prisma/client';
abstract class BaseRepository<T, CreateInput, UpdateInput> {
constructor(protected prisma: PrismaClient) {}
abstract findAll(): Promise<T[]>;
abstract findById(id: string): Promise<T | null>;
abstract create(data: CreateInput): Promise<T>;
abstract update(id: string, data: UpdateInput): Promise<T>;
abstract delete(id: string): Promise<void>;
}
// Implementación específica
class UserRepository extends BaseRepository<
User,
Omit<User, 'id' | 'createdAt' | 'updatedAt'>,
Partial<Omit<User, 'id'>>
> {
async findAll() {
return this.prisma.user.findMany({
orderBy: { createdAt: 'desc' }
});
}
async findById(id: string) {
return this.prisma.user.findUnique({
where: { id },
include: { profile: true }
});
}
async findByEmail(email: string) {
return this.prisma.user.findUnique({
where: { email }
});
}
async create(data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>) {
return this.prisma.user.create({
data
});
}
async update(id: string, data: Partial<Omit<User, 'id'>>) {
return this.prisma.user.update({
where: { id },
data: {
...data,
updatedAt: new Date()
}
});
}
async delete(id: string) {
await this.prisma.user.delete({
where: { id }
});
}
}
2. Service Layer con Dependency Injection
// Service con lógica de negocio
import { injectable, inject } from 'tsyringe';
@injectable()
class UserService {
constructor(
@inject('UserRepository') private userRepo: UserRepository,
@inject('EmailService') private emailService: EmailService,
@inject('CacheService') private cache: CacheService
) {}
async registerUser(input: RegisterUserInput): Promise<User> {
// Validar que email no existe
const existing = await this.userRepo.findByEmail(input.email);
if (existing) {
throw new ConflictError('Email already registered');
}
// Hash password
const hashedPassword = await bcrypt.hash(input.password, 12);
// Crear usuario
const user = await this.userRepo.create({
...input,
password: hashedPassword
});
// Enviar email de bienvenida
await this.emailService.sendWelcome(user.email, user.name);
// Invalidar cache de lista de usuarios
await this.cache.invalidate('users:list');
return user;
}
async getUserById(id: string): Promise<User> {
// Intentar cache primero
const cached = await this.cache.get<User>(`users:${id}`);
if (cached) {
return cached;
}
const user = await this.userRepo.findById(id);
if (!user) {
throw new NotFoundError('User not found');
}
// Guardar en cache
await this.cache.set(`users:${id}`, user, 3600);
return user;
}
}3. Error Handling Tipado
// Sistema de errores customizado
abstract class AppError extends Error {
abstract statusCode: number;
abstract code: string;
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
toJSON() {
return {
error: {
code: this.code,
message: this.message
}
};
}
}
class NotFoundError extends AppError {
statusCode = 404;
code = 'NOT_FOUND';
}
class ValidationError extends AppError {
statusCode = 400;
code = 'VALIDATION_ERROR';
constructor(
message: string,
public fields?: Record<string, string[]>
) {
super(message);
}
toJSON() {
return {
error: {
code: this.code,
message: this.message,
fields: this.fields
}
};
}
}
class UnauthorizedError extends AppError {
statusCode = 401;
code = 'UNAUTHORIZED';
}
// Middleware de error handling
function errorHandler(
error: Error,
req: Request,
res: Response,
next: NextFunction
) {
if (error instanceof AppError) {
return res.status(error.statusCode).json(error.toJSON());
}
// Error no esperado
console.error('Unexpected error:', error);
return res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred'
}
});
}
Frameworks Server-First con TypeScript
Next.js App Router + Server Actions
// app/actions/users.ts
'use server'
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email()
});
export async function createUser(formData: FormData) {
const rawData = {
name: formData.get('name'),
email: formData.get('email')
};
// Validación server-side
const result = createUserSchema.safeParse(rawData);
if (!result.success) {
return {
error: result.error.flatten().fieldErrors
};
}
// Crear usuario
const user = await db.users.create({
data: result.data
});
// Revalidar cache
revalidatePath('/users');
return { user };
}
// app/users/page.tsx
import { createUser } from '../actions/users';
export default async function UsersPage() {
const users = await db.users.findMany();
return (
<div>
<h1>Users</h1>
<form action={createUser}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Create User</button>
</form>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}Conclusión
TypeScript transformó el backend development. Con type-safety end-to-end, patrones modernos, y frameworks maduros, es la elección obvia para desarrollo server-first en 2025.
Si quieres ver más sobre frameworks modernos, confiere React vs Vue: Cuál Framework Elegir en 2025, donde exploramos opciones de frontend que combinan perfectamente con backends TypeScript.
¡Vamos a por ello! 🦅
📚 ¿Quieres Dominar JavaScript y TypeScript?
Este artículo mostró patrones avanzados de backend, pero dominar JavaScript es fundamental para entender TypeScript profundamente.
Material de Estudio Completo
Preparé un guía completo de JavaScript de básico a avanzado:
Opciones de inversión:
- $9.90 USD (pago único)
💡 Base sólida para dominar TypeScript

