Volver al blog

TypeScript-First: Por qué Todo Proyecto Node.js Comienza con TypeScript en 2025

Hola HaWkers, si todavía estás creando proyectos Node.js nuevos con JavaScript puro en 2025, necesito contarte: estás nadando contra la corriente. TypeScript-first se convirtió en el estándar absoluto de la industria, y con razón.

La pregunta ya no es "¿debo usar TypeScript?" sino "¿por qué no usaría TypeScript?". Vamos a explorar este cambio fundamental que está definiendo el desarrollo moderno.

La Revolución TypeScript-First en Node.js

En 2025, la mayoría aplastante de los nuevos proyectos Node.js comienza con TypeScript. Esto no es solo una tendencia pasajera - es un cambio fundamental en la forma en que desarrollamos aplicaciones backend.

Los Números No Mienten

// Estadísticas de adopción en 2025
const typescriptAdoption = {
  newProjects: '87%', // Proyectos nuevos usando TS
  migrations: '45%',  // Proyectos JS siendo migrados
  jobRequirements: '92%', // Vacantes exigiendo TS
  npmPackages: '76%' // Top packages con tipos
};

// Principales frameworks ya son TypeScript-first
const frameworkSupport = {
  nestjs: 'TypeScript nativo',
  fastify: 'Tipos first-class',
  trpc: 'TypeScript-only',
  prisma: 'TypeScript-first ORM',
  nextjs: 'TypeScript recomendado'
};

¿Por Qué TypeScript Ganó?

1. Soporte Nativo de Node.js

Node.js ahora tiene soporte experimental para ejecutar TypeScript directamente:

// ¡Ejecuta TypeScript directamente sin compilación!
// node --experimental-strip-types server.ts

import express from 'express';
import type { Request, Response } from 'express';

const app = express();

interface User {
  id: string;
  name: string;
  email: string;
}

app.get('/api/users/:id', async (req: Request, res: Response) => {
  const userId = req.params.id;

  const user: User = await db.users.findUnique({
    where: { id: userId }
  });

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.json(user);
});

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

2. Prevención de Bugs en Tiempo de Desarrollo

TypeScript captura errores que solo aparecerían en producción con JavaScript:

// Ejemplo real: API de pago
interface PaymentRequest {
  amount: number;
  currency: 'USD' | 'EUR' | 'BRL';
  customerId: string;
  metadata?: Record<string, string>;
}

class PaymentService {
  async processPayment(request: PaymentRequest) {
    // TypeScript te obliga a manejar todos los casos
    if (request.amount <= 0) {
      throw new Error('Amount must be positive');
    }

    // Autocomplete te muestra solo monedas válidas
    const exchangeRate = this.getExchangeRate(request.currency);

    return {
      transactionId: generateId(),
      amount: request.amount,
      currency: request.currency,
      status: 'processed' as const
    };
  }

  private getExchangeRate(currency: PaymentRequest['currency']): number {
    // Error de compilación si intentas pasar moneda inválida
    const rates = {
      USD: 1.0,
      EUR: 1.1,
      BRL: 0.2
    };

    return rates[currency];
  }
}

// Uso - TypeScript valida en tiempo de desarrollo
const service = new PaymentService();

// ✅ Correcto
await service.processPayment({
  amount: 100,
  currency: 'USD',
  customerId: 'cus_123'
});

// ❌ Error de compilación: "JPY" no es moneda válida
await service.processPayment({
  amount: 100,
  currency: 'JPY', // ¡TypeScript impide esto!
  customerId: 'cus_123'
});

3. Refactorización Segura y Confiable

// Cambios seguros en código grande
interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  // Agrega nuevo campo obligatorio
  sku: string;
}

// TypeScript inmediatamente muestra todos los lugares que
// necesitan ser actualizados para incluir SKU
function createProduct(data: Omit<Product, 'id'>): Product {
  return {
    id: generateId(),
    ...data
  };
}

// Error de compilación si no pasas SKU
const product = createProduct({
  name: 'Laptop',
  price: 999,
  category: 'Electronics'
  // Missing 'sku' - ¡TypeScript no deja pasar!
});

Setup Moderno de TypeScript en 2025

Configuración Optimizada

// tsconfig.json - Configuración moderna y optimizada
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2023"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Package.json Moderno

{
  "name": "modern-typescript-api",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js",
    "test": "vitest",
    "lint": "eslint src --ext .ts",
    "format": "prettier --write \"src/**/*.ts\""
  },
  "dependencies": {
    "fastify": "^4.25.0",
    "@fastify/cors": "^8.5.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "tsx": "^4.7.0",
    "typescript": "^5.3.0",
    "vitest": "^1.0.0",
    "eslint": "^8.56.0",
    "@typescript-eslint/parser": "^6.17.0",
    "@typescript-eslint/eslint-plugin": "^6.17.0",
    "prettier": "^3.1.0"
  }
}

API Moderna con Fastify + TypeScript

// src/server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import { z } from 'zod';

const server = Fastify({
  logger: true
});

await server.register(cors);

// Schema validation con Zod
const CreateUserSchema = z.object({
  name: z.string().min(3),
  email: z.string().email(),
  age: z.number().int().positive()
});

type CreateUserInput = z.infer<typeof CreateUserSchema>;

// Route handlers tipados
server.post<{ Body: CreateUserInput }>(
  '/users',
  async (request, reply) => {
    // Valida y parsea automáticamente
    const userData = CreateUserSchema.parse(request.body);

    const user = await db.users.create({
      data: userData
    });

    return reply.code(201).send(user);
  }
);

server.get('/users/:id', async (request, reply) => {
  const { id } = request.params as { id: string };

  const user = await db.users.findUnique({
    where: { id }
  });

  if (!user) {
    return reply.code(404).send({ error: 'User not found' });
  }

  return user;
});

const start = async () => {
  try {
    await server.listen({ port: 3000 });
    console.log('Server listening on http://localhost:3000');
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
};

start();

TypeScript con Prisma: Type Safety End-to-End

// prisma/schema.prisma
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
  createdAt DateTime @default(now())
}

// src/repositories/user.repository.ts
import { PrismaClient, Prisma } from '@prisma/client';

const prisma = new PrismaClient();

export class UserRepository {
  // ¡Prisma genera tipos automáticamente!
  async findById(id: string) {
    return prisma.user.findUnique({
      where: { id },
      include: {
        posts: {
          where: { published: true }
        }
      }
    });
  }

  async create(data: Prisma.UserCreateInput) {
    return prisma.user.create({
      data,
      include: { posts: true }
    });
  }

  async findWithPosts(options: {
    skip?: number;
    take?: number;
  }) {
    return prisma.user.findMany({
      ...options,
      include: {
        posts: {
          orderBy: { createdAt: 'desc' }
        }
      }
    });
  }
}

// ¡Types inferidos automáticamente por Prisma!
const repo = new UserRepository();
const user = await repo.findById('123');
// user tiene tipo completo: User & { posts: Post[] }

Tests Tipados con Vitest

// src/services/user.service.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
  });

  it('should create user with valid data', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com',
      age: 30
    };

    const user = await service.createUser(userData);

    expect(user).toBeDefined();
    expect(user.id).toBeTruthy();
    expect(user.email).toBe(userData.email);
  });

  it('should throw error for invalid email', async () => {
    const userData = {
      name: 'John Doe',
      email: 'invalid-email',
      age: 30
    };

    await expect(
      service.createUser(userData)
    ).rejects.toThrow('Invalid email');
  });

  it('should find user by id', async () => {
    const created = await service.createUser({
      name: 'Jane Doe',
      email: 'jane@example.com',
      age: 25
    });

    const found = await service.findById(created.id);

    expect(found).toEqual(created);
  });
});

Ventajas Competitivas en el Mercado

1. Código Más Seguro y Mantenible

// Cambios propagan automáticamente
type UserRole = 'admin' | 'user' | 'moderator';

function getUserPermissions(role: UserRole) {
  // Si agregas nueva role, TypeScript te obliga a
  // actualizar todos los lugares que usan roles
  switch (role) {
    case 'admin':
      return ['read', 'write', 'delete', 'admin'];
    case 'moderator':
      return ['read', 'write', 'moderate'];
    case 'user':
      return ['read'];
    // TypeScript garantiza que todos los casos están cubiertos
  }
}

2. Mejor Experiencia de Desarrollo

  • Autocomplete inteligente - IDE sabe exactamente lo que está disponible
  • Refactorización segura - Rename functions/variables sin miedo
  • Documentación inline - Tipos son documentación viva
  • Detección de errores instantánea - Bugs capturados en segundos, no en producción

3. Colaboración en Equipo

// Otros desarrolladores entienden tu código instantáneamente
interface CreateOrderDTO {
  userId: string;
  items: Array<{
    productId: string;
    quantity: number;
    price: number;
  }>;
  shippingAddress: {
    street: string;
    city: string;
    state: string;
    zipCode: string;
    country: string;
  };
  paymentMethod: 'credit_card' | 'debit_card' | 'pix';
}

// No necesita leer documentación - tipos explican todo
function createOrder(data: CreateOrderDTO): Promise<Order> {
  // Implementación...
}

Desafíos y Cómo Superarlos

1. Curva de Aprendizaje

Solución: Comienza simple, agrega complejidad gradualmente

// Nivel 1: Tipos básicos
function add(a: number, b: number): number {
  return a + b;
}

// Nivel 2: Interfaces
interface User {
  id: string;
  name: string;
}

// Nivel 3: Generics
function findById<T>(items: T[], id: string): T | undefined {
  return items.find(item => (item as any).id === id);
}

// Nivel 4: Advanced types
type Nullable<T> = T | null;
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };

2. Tiempo de Compilación

Solución: Usa herramientas modernas como tsx para desarrollo rápido

# Desarrollo instantáneo
tsx watch src/server.ts

# Build optimizado para producción
tsc --build --incremental

El Futuro es TypeScript-First

En 2025, TypeScript no es opcional - es esperado. Empresas buscan desarrolladores con TypeScript, proyectos open source migran a TypeScript, y nuevas herramientas nacen TypeScript-first.

Si todavía no dominas TypeScript, este es el momento de invertir en esa habilidad. No es solo sobre agregar tipos a JavaScript - es sobre pensar diferente, sobre construir software más robusto y mantenible.

Para entender mejor el ecosistema moderno de Node.js, recomiendo: Serverless en 2025: Cómo Node.js Domina la Arquitectura Sin Servidor donde exploramos cómo TypeScript y Node.js juntos dominan serverless.

¡Vamos a por ello! 🦅

Domina JavaScript de Verdad

TypeScript es poderoso, pero está construido sobre JavaScript. Dominar JavaScript profundamente es esencial para usar TypeScript efectivamente.

Preparé un material completo para que domines JavaScript:

Formas de pago:

  • $9.90 USD (pago único)

Ver Contenido Completo

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios