// Manual de Desenvolvimento Fullstack Moderno · 2025

Do zero ao
desenvolvedor
fullstack.

Um guia gradativo e completo cobrindo as tecnologias que dominam o mercado hoje — do ambiente de desenvolvimento até deploy em produção.

15
Capítulos
3
Níveis
Exemplos
CAP. 01 Básico
Como a Web Funciona
O modelo mental que todo dev precisa ter antes de escrever a primeira linha

Antes de código, você precisa entender o fluxo completo de uma requisição. Cada camada tem responsabilidades distintas e saber onde cada problema ocorre poupa horas de debug.

O Ciclo Completo de uma Requisição

1
DNS Resolution
O browser converte meusite.com em um IP (ex: 192.168.1.1) consultando servidores DNS hierarquicamente.
2
TCP Handshake + TLS
Conexão TCP é estabelecida (SYN → SYN-ACK → ACK). Se HTTPS, negociação TLS acontece aqui — certificado, chaves, cipher suite.
3
HTTP Request
Browser envia: método (GET/POST...), URL, headers (Accept, Cookie, Authorization...) e body opcional.
4
Server Processing
Servidor processa: roteamento, middleware, autenticação, query no banco, lógica de negócio.
5
HTTP Response
Servidor responde: status code (200, 404, 500...), headers de resposta, corpo (HTML, JSON, stream...).
6
Browser Rendering
Parse HTML → DOM tree. Parse CSS → CSSOM. Juntos formam o Render Tree → Layout → Paint → Composite.
Status CodeSignificadoQuando ocorre
200 OKSucessoRequisição processada com sucesso
201 CreatedRecurso criadoPOST bem-sucedido (criou entidade no banco)
301/302RedirectURL movida permanente/temporariamente
400 Bad RequestErro do clientePayload inválido, falta de campo obrigatório
401 UnauthorizedNão autenticadoToken ausente ou expirado
403 ForbiddenSem permissãoAutenticado mas sem acesso ao recurso
404 Not FoundNão encontradoRota ou recurso inexistente
422 UnprocessableValidação falhouDados bem formados mas semanticamente inválidos
500 Server ErrorErro internoExceção não tratada no servidor

CAP. 02 Básico
Git & Versionamento
O mínimo que você precisa dominar para trabalhar em equipe (e sozinho)

Configuração Inicial

terminal
$ git config --global user.name "Seu Nome"
$ git config --global user.email "seu@email.com"
$ git config --global core.editor "code --wait"
$ git config --global init.defaultBranch main

Comandos Fundamentais

ComandoO que faz
git initInicia repositório na pasta atual
git clone <url>Clona repositório remoto
git statusMostra arquivos modificados/staged
git add .Adiciona todos os arquivos ao staging
git commit -m "msg"Cria snapshot com mensagem
git push origin mainEnvia commits para o remote
git pullBusca + merge do remote
git log --onelineHistórico compacto
git diffDiff do que ainda não foi staged
git stashGuarda mudanças temporariamente
git reset --soft HEAD~1Desfaz último commit, mantém staging

Branches & Fluxo de Trabalho

git flow básico
$ git checkout -b feature/login # cria e muda para branch
$ git add . && git commit -m "feat: add login form"
$ git push -u origin feature/login # push + tracking
# Abre Pull Request no GitHub/GitLab
# Após review e merge:
$ git checkout main && git pull
$ git branch -d feature/login # limpa branch local
// Conventional Commits
Padrão de mensagens de commit amplamente adotado: feat: nova funcionalidade, fix: correção de bug, docs: documentação, chore: manutenção, refactor: refatoração, test: testes. Muitas ferramentas de changelog automático dependem disso.

Merge vs Rebase

git merge (preserva história)
A---B---C  main
     \       \
      D---E---M  ← merge commit
git rebase (história linear)
A---B---C---D'---E'  main
← commits reescritos no topo
// regra de ouro
Nunca faça rebase em branches públicas/compartilhadas. Rebase reescreve o histórico — se outros já têm os commits originais, você vai criar conflitos impossíveis. Use rebase só em branches locais e pessoais.

.gitignore — O Essencial

.gitignore
# Dependências
node_modules/
.pnp
.pnp.js

# Build outputs
dist/
.next/
out/
build/

# Variáveis de ambiente
.env
.env.local
.env.*.local

# IDEs
.vscode/settings.json
.idea/
*.swp

# OS
.DS_Store
Thumbs.db

# Logs
*.log
npm-debug.log*

CAP. 03 Básico
Ambiente & Ferramentas
Node.js, gerenciadores de pacotes e seu setup de desenvolvimento
🟢

Node.js

Runtime JS no servidor. Instale via nvm (Node Version Manager) para gerenciar múltiplas versões.

📦

npm / pnpm / yarn

Gerenciadores de pacotes. pnpm é o mais eficiente hoje em dia — economiza espaço em disco com links simbólicos.

🔷

VS Code

Editor padrão da indústria. Extensões essenciais: ESLint, Prettier, GitLens, Tailwind IntelliSense.

🦊

DevTools

F12 no browser. Aprenda: Elements, Console, Network (timing de requests), Sources (debugging) e Performance.

setup inicial
# Instalar nvm (Linux/Mac)
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
$ nvm install --lts # instala LTS mais recente
$ nvm use --lts

# Instalar pnpm
$ npm install -g pnpm

# Criar projeto React com Vite
$ pnpm create vite meu-projeto --template react-ts
$ cd meu-projeto && pnpm install
$ pnpm dev # inicia em localhost:5173

package.json — O Mapa do Projeto

package.json
{
  "name": "meu-projeto",
  "version": "1.0.0",
  "scripts": {
    "dev":     "vite",
    "build":   "tsc && vite build",
    "preview": "vite preview",
    "lint":    "eslint . --ext ts,tsx",
    "format":  "prettier --write ."
  },
  "dependencies": {          // vai para produção
    "react": "^18.3.0",
    "react-dom": "^18.3.0"
  },
  "devDependencies": {       // só no desenvolvimento
    "vite": "^5.0.0",
    "typescript": "^5.3.0",
    "@types/react": "^18.3.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.0"
  }
}
// pnpm vs npm vs yarn
pnpm é recomendado: até 60% mais rápido, usa hard links para não duplicar packages entre projetos, e tem suporte nativo a monorepos com workspaces. No dia a dia, os comandos são quase idênticos ao npm.

CAP. 04 Básico
HTML & CSS Moderno
O que mudou nos últimos anos e o que você precisa dominar

HTML Semântico

Usar as tags certas não é frescura — afeta SEO, acessibilidade (leitores de tela) e manutenibilidade.

html
<!-- ❌ Não semântico -->
<div class="header">
  <div class="nav"></div>
</div>
<div class="content">
  <div class="article"></div>
</div>

<!-- ✅ Semântico -->
<header>
  <nav></nav>
</header>
<main>
  <article>
    <h1>Título</h1>
    <section></section>
    <aside></aside>
  </article>
</main>
<footer></footer>

CSS Layout: Flexbox & Grid

css
/* Flexbox — 1D: linha OU coluna */
.flex-container {
  display: flex;
  align-items: center;      /* eixo cruzado */
  justify-content: space-between; /* eixo principal */
  gap: 1rem;
  flex-wrap: wrap;
}

/* Grid — 2D: linhas E colunas */
.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1.5rem;
}

/* CSS Custom Properties (variáveis) */
:root {
  --color-primary: #c94a1e;
  --spacing-md: 1rem;
  --radius: 6px;
}

.button {
  background: var(--color-primary);
  padding: var(--spacing-md);
  border-radius: var(--radius);
}

/* Media queries modernas */
@media (width >= 768px) {
  .container { max-width: 1200px; }
}

/* Container queries (CSS moderno!) */
.card-container { container-type: inline-size; }
@container (min-width: 400px) {
  .card { display: grid; grid-template-columns: 1fr 2fr; }
}

CAP. 05 Intermediário
Tailwind CSS
Utility-first CSS — o jeito que a indústria adotou para estilizar interfaces

Tailwind não é CSS inline glorificado. É um sistema de design consistente onde você compõe classes utilitárias ao invés de escrever CSS custom. O resultado é mais rápido de escrever e mais fácil de manter.

Setup com Vite

terminal
$ pnpm add -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
tailwind.config.ts
import type { Config } from "tailwindcss";

export default {
  content: ["./index.html", "./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      colors: {
        brand: {
          "50":  "#fef3ee",
          "500": "#c94a1e",
          "900": "#7c1d07",
        },
      },
      fontFamily: {
        sans: ["Outfit", "system-ui"],
      },
    },
  },
  plugins: [],
} satisfies Config;

Classes Fundamentais que Você Usará Todo Dia

CategoriaClassesEquivalente CSS
Displayflex grid hidden blockdisplay: flex etc.
Flexboxitems-center justify-between gap-4 flex-wrapalign-items, justify-content...
Gridgrid-cols-3 col-span-2 gap-6grid-template-columns...
Spacingp-4 px-6 mt-8 mx-autopadding, margin (1 unit = 4px)
Textotext-lg font-bold text-gray-600 leading-relaxedfont-size, font-weight...
Backgroundbg-white bg-brand-500 bg-gradient-to-rbackground
Borderborder border-gray-200 rounded-lg ring-2border, border-radius...
Shadowshadow-sm shadow-lg shadow-xlbox-shadow
Responsivemd:flex lg:grid-cols-4 sm:text-baseMedia queries
Statehover:bg-red-600 focus:ring disabled:opacity-50Pseudo-seletores
Dark modedark:bg-gray-900 dark:text-whiteMedia prefers-color-scheme
tsx
// Componente de botão com Tailwind — pattern real
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const button = cva(
  "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50",
  {
    variants: {
      variant: {
        primary:   "bg-brand-500 text-white hover:bg-brand-600 focus:ring-brand-500",
        secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-400",
        ghost:     "hover:bg-gray-100 focus:ring-gray-300",
        danger:    "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
      },
      size: {
        sm: "h-8 px-3 text-sm",
        md: "h-10 px-4 text-sm",
        lg: "h-12 px-6 text-base",
      },
    },
    defaultVariants: { variant: "primary", size: "md" },
  }
);

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>
  & VariantProps<typeof button>;

export function Button({ variant, size, className, ...props }: ButtonProps) {
  return <button className={cn(button({ variant, size }), className)} {...props} />;
}
// cn() helper — indispensável
Instale clsx + tailwind-merge e crie o utilitário: export const cn = (...inputs) => twMerge(clsx(inputs)). Ele resolve conflitos de classes Tailwind (ex: p-4 p-6 → vira p-6) e condiciona classes de forma limpa.

CAP. 06 Intermediário
React Moderno
Hooks, padrões de componentes e como pensar em React 18+
tsx
// Regras que NUNCA podem ser quebradas:
// 1. Hooks só no top-level (não dentro de if/loops)
// 2. Hooks só em componentes React ou custom hooks
// 3. Nomes de custom hooks começam com "use"

import { useState, useEffect, useCallback, useMemo, useRef } from "react";

// useState — estado local
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

// useEffect — efeitos colaterais
useEffect(() => {
  // roda após render
  const sub = subscribe(userId);
  return () => sub.unsubscribe(); // cleanup
}, [userId]); // dependency array

// useCallback — memoiza funções (evita re-criação)
const handleSubmit = useCallback((data: FormData) => {
  mutation.mutate(data);
}, [mutation]);

// useMemo — memoiza valores caros
const sortedItems = useMemo(
  () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// useRef — referência mutável, não causa re-render
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { inputRef.current?.focus(); }, []);

Padrões de Composição

tsx
// Compound components — API fluente e flexível
const Card = ({ children }: { children: ReactNode }) =>
  <div className="rounded-xl border bg-white shadow-sm">{children}</div>;

Card.Header = ({ children }: { children: ReactNode }) =>
  <div className="border-b p-4 font-semibold">{children}</div>;

Card.Body = ({ children }: { children: ReactNode }) =>
  <div className="p-4">{children}</div>;

Card.Footer = ({ children }: { children: ReactNode }) =>
  <div className="border-t p-4">{children}</div>;

// Uso:
<Card>
  <Card.Header>Título</Card.Header>
  <Card.Body>Conteúdo</Card.Body>
  <Card.Footer>Rodapé</Card.Footer>
</Card>

// Render props / children como função
function DataTable<T>({ data, renderRow }: {
  data: T[];
  renderRow: (item: T, index: number) => ReactNode;
}) {
  return <table><tbody>{data.map(renderRow)}</tbody></table>;
}

Performance: Quando e Como Otimizar

// não otimize antes de medir
React.memo, useMemo e useCallback têm custo. Só use quando o React DevTools Profiler mostrar re-renders desnecessários custosos. Otimização prematura cria código mais complexo sem benefício real.
tsx
// React.memo — evita re-render se props não mudaram
const ExpensiveRow = React.memo(({ item }: { item: Item }) => {
  return <tr>...</tr>;
});

// Lazy loading — code splitting automático
const HeavyChart = React.lazy(() => import("./HeavyChart"));

function Dashboard() {
  return (
    <React.Suspense fallback={<Skeleton />}>
      <HeavyChart />
    </React.Suspense>
  );
}

// useTransition — marca update como não-urgente (React 18)
const [isPending, startTransition] = useTransition();
startTransition(() => setSearchQuery(value)); // não bloqueia UI

CAP. 07 Intermediário
Bibliotecas de UI
Headless, styled e quando usar cada uma
BibliotecaTipoQuando usar
shadcn/uiHeadless + Radix + TailwindProjetos modernos. Código é seu — não é dependência
Radix UIHeadless (sem estilo)Quando você quer total controle do estilo
Headless UIHeadless (Tailwind Labs)Projetos Tailwind, componentes simples
Material UI (MUI)Styled completoProjetos corporativos com design system Material
MantineStyled completoProdutividade máxima, muitos componentes prontos
Ant DesignStyled completoDashboards admin, mercado asiático/enterprise

shadcn/ui — O Padrão Moderno

shadcn/ui não é uma biblioteca de pacotes — você copia os componentes para dentro do seu projeto. Isso significa controle total, sem ficar preso a versões externas.

terminal
$ pnpm dlx shadcn@latest init
$ pnpm dlx shadcn@latest add button # copia Button para src/components/ui/
$ pnpm dlx shadcn@latest add dialog form table
tsx
// shadcn/ui — uso com Form (react-hook-form + zod)
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";

const schema = z.object({
  email: z.string().email("Email inválido"),
  senha: z.string().min(8, "Mínimo 8 caracteres"),
});

export function LoginForm() {
  const form = useForm({ resolver: zodResolver(schema) });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl><Input {...field} /></FormControl>
              <FormMessage />  {/* erro automático do zod */}
            </FormItem>
          )}
        />
      </form>
    </Form>
  );
}

CAP. 08 Avançado
Estado & Data Fetching
Gerenciamento de estado global e sincronização com servidor
FerramentaPara que serveUse quando
TanStack QueryServer state (cache, refetch, loading)SEMPRE que buscar dados de API
ZustandClient state global simplesEstado compartilhado entre componentes distantes
JotaiAtomic state (bottom-up)Estado granular, derivado de átomos
Redux ToolkitState + DevTools + RTK QueryApps complexas, time grande, muito state
Context APIInjeção de dependênciaEstado que raramente muda (tema, auth, config)

TanStack Query — O Padrão para Server State

tsx
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";

// Setup — uma vez na raiz da app
const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 1000 * 60, retry: 2 },
  },
});

// Query — busca e cacheia dados
function useUsers(page: number) {
  return useQuery({
    queryKey: ["users", page],
    queryFn: () => fetchUsers(page),
    placeholderData: keepPreviousData, // evita flash ao paginar
  });
}

// Mutation — cria/atualiza/deleta + invalida cache
function useCreateUser() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (data: CreateUserDto) => api.post("/users", data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["users"] });
    },
    onError: (err) => toast.error(err.message),
  });
}

// No componente:
const { data, isLoading, isError, error } = useUsers(currentPage);
const createUser = useCreateUser();

Zustand — Estado Global Simples

typescript
import { create } from "zustand";
import { persist } from "zustand/middleware";

type CartStore = {
  items: CartItem[];
  total: number;
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
  clear: () => void;
};

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      total: 0,
      addItem(item) {
        set(s => ({
          items: [...s.items, item],
          total: s.total + item.price,
        }));
      },
      removeItem(id) {
        set(s => {
          const item = s.items.find(i => i.id === id);
          return {
            items: s.items.filter(i => i.id !== id),
            total: s.total - (item?.price ?? 0),
          };
        });
      },
      clear() { set({ items: [], total: 0 }); },
    }),
    { name: "cart" } // persiste no localStorage
  )
);

CAP. 09 Intermediário
Vite & Build Tools
O ecossistema de ferramentas de build moderno

Vite é o bundler padrão para SPAs modernas. Usa ESModules nativos em dev (sem bundle = startup instantâneo) e Rollup em produção (bundle otimizado).

vite.config.ts — Configuração Completa

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],

  resolve: {
    alias: { "@": path.resolve(__dirname, "./src") },
  },

  server: {
    port: 5173,
    proxy: {
      "/api": {
        target: "http://localhost:3001",
        changeOrigin: true,
      },
    },
  },

  build: {
    outDir: "dist",
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor:  ["react", "react-dom"],
          ui:      ["@radix-ui/react-dialog", "lucide-react"],
          query:   ["@tanstack/react-query"],
        },
      },
    },
  },
});

ESLint + Prettier — Qualidade de Código

eslint.config.js
import js from "@eslint/js";
import ts from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";

export default ts.config(
  js.configs.recommended,
  ...ts.configs.recommended,
  {
    plugins: { "react-hooks": reactHooks },
    rules: {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
      "@typescript-eslint/no-explicit-any": "warn",
      "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    },
  }
);

CAP. 10 Avançado
Next.js — App Router
O framework fullstack React para produção

Next.js é o framework React mais usado em produção. O App Router (Next 13+) unifica SSR, SSG, ISR e Server Components em um único modelo mental baseado em arquivos.

Estrutura de Pastas

text
app/
├── layout.tsx          ← root layout (html, body, providers)
├── page.tsx            ← rota /
├── loading.tsx         ← Suspense fallback automático
├── error.tsx           ← Error Boundary automático
├── not-found.tsx       ← página 404
├── globals.css
│
├── (auth)/             ← route group (não aparece na URL)
│   ├── login/page.tsx  ← rota /login
│   └── signup/page.tsx ← rota /signup
│
├── dashboard/
│   ├── layout.tsx      ← layout do dashboard
│   ├── page.tsx        ← rota /dashboard
│   └── [id]/
│       └── page.tsx    ← rota /dashboard/:id
│
└── api/
    └── users/
        └── route.ts    ← GET /api/users, POST /api/users

Server vs Client Components

Server Component (padrão)Client Component ('use client')
Onde rodaServidor (Node.js)Browser (+ hidratação)
Acesso a DB✅ Direto❌ Via API
useState/hooks
Event handlers
Bundle JS0kb no clienteIncluído no bundle
SEO✅ HTML completo⚠️ Depende
app/posts/page.tsx
// Server Component — async, busca dado direto
import { db } from "@/lib/db";
import { PostCard } from "./PostCard";

export default async function PostsPage() {
  const posts = await db.post.findMany({
    where: { published: true },
    orderBy: { createdAt: "desc" },
    take: 20,
  });

  return (
    <main className="container mx-auto py-8">
      <h1 className="text-3xl font-bold mb-6">Posts</h1>
      <div className="grid grid-cols-3 gap-6">
        {posts.map(post => <PostCard key={post.id} post={post} />)}
      </div>
    </main>
  );
}

// PostCard.tsx — Client Component (tem interatividade)
"use client";

export function PostCard({ post }: { post: Post }) {
  const [liked, setLiked] = useState(false);
  return (
    <article>
      <h2>{post.title}</h2>
      <button onClick={() => setLiked(l => !l)}>
        {liked ? "❤️" : "🤍"}
      </button>
    </article>
  );
}

Caching no Next.js 15

typescript
// fetch com opções de cache
// Static (SSG) — cacheia para sempre
fetch("/api/config", { cache: "force-cache" });

// Dynamic (SSR) — nunca cacheia
fetch("/api/live",   { cache: "no-store" });

// ISR — revalida a cada N segundos
fetch("/api/posts",  { next: { revalidate: 60 } });

// Tags para revalidação sob demanda
fetch("/api/posts",  { next: { tags: ["posts"] } });

// Em server action ou API route:
import { revalidateTag } from "next/cache";
revalidateTag("posts"); // invalida todas as fetches com tag "posts"

CAP. 11 Intermediário
Node.js & APIs REST
Backend com Express/Fastify, middlewares e boas práticas de API design

Para APIs standalone (sem Next.js), Fastify é a escolha moderna — mais rápido que Express, TypeScript nativo, schema validation integrado. Para projetos simples ou legado, Express ainda é válido.

API REST com Fastify + Zod

src/server.ts
import Fastify from "fastify";
import { z } from "zod";
import { db } from "./db";

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

// Plugin de CORS
await app.register(import("@fastify/cors"), {
  origin: ["http://localhost:5173"],
});

// Schema Zod → Fastify
const CreatePostBody = z.object({
  title:   z.string().min(1).max(200),
  content: z.string().min(10),
  tags:    z.array(z.string()).optional(),
});

// GET /posts
app.get("/posts", async (req, reply) => {
  const { page = 1, limit = 10 } = req.query as { page?: number; limit?: number };
  const [posts, total] = await Promise.all([
    db.post.findMany({ skip: (page - 1) * limit, take: limit }),
    db.post.count(),
  ]);
  return { posts, total, page, pages: Math.ceil(total / limit) };
});

// POST /posts
app.post("/posts", async (req, reply) => {
  const parsed = CreatePostBody.safeParse(req.body);
  if (!parsed.success) {
    return reply.status(422).send({ errors: parsed.error.flatten() });
  }
  const post = await db.post.create({ data: parsed.data });
  return reply.status(201).send(post);
});

await app.listen({ port: 3001 });

Boas Práticas de API Design

PráticaErrado ❌Certo ✅
Verbos HTTPPOST /getUserGET /users/:id
Nomes de recursos/getUsers /createPost/users /posts (substantivos)
Plural/user /post/users /posts
Nested resources/getPosts?userId=1/users/1/posts
Erros{ success: false, msg: "err" }{ error: { code, message, details } }
PaginaçãoRetornar tudo{ data, total, page, limit, pages }
VersioningQuebrar clientes/api/v1/users/api/v2/users

CAP. 12 Intermediário
Banco de Dados & ORM
Prisma, PostgreSQL e o modelo de dados moderno
🐘

PostgreSQL

Banco relacional padrão para aplicações sérias. ACID compliant, JSON nativo, full-text search.

🔺

Prisma ORM

ORM TypeScript-first com schema declarativo e client 100% tipado. Padrão do ecossistema Next.js.

🔥

Drizzle ORM

ORM SQL-like, mais leve que Prisma. Melhor performance e controle granular de queries.

🍃

MongoDB

Banco de documentos. Bom para schemas flexíveis, mas cuidado com consistência em dados relacionais.

Prisma — Setup e Operações

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String
  role      Role     @default(USER)
  posts     Post[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@map("users")  // nome na tabela do banco
}

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

  @@index([authorId])
  @@map("posts")
}

enum Role {
  USER
  ADMIN
  MODERATOR
}
lib/queries.ts
import { db } from "./db";

// SELECT com relacionamento
const postsComAutor = await db.post.findMany({
  where: { published: true, author: { role: "ADMIN" } },
  include: { author: { select: { name: true, email: true } } },
  orderBy: { createdAt: "desc" },
  take: 10, skip: 0,
});

// UPDATE com where
const updated = await db.post.update({
  where: { id: postId, authorId: userId }, // segurança
  data: { title: newTitle, updatedAt: new Date() },
});

// Transaction — atomicidade garantida
const [user, post] = await db.$transaction([
  db.user.create({ data: userData }),
  db.post.create({ data: { ...postData, authorId: userId } }),
]);

// Aggregate
const stats = await db.order.aggregate({
  _sum: { total: true },
  _avg: { total: true },
  _count: true,
  where: { createdAt: { gte: startOfMonth } },
});

CAP. 13 Avançado
Auth & Segurança
JWT, sessions, OAuth e as armadilhas que todo dev cai
EstratégiaComo funcionaMelhor para
JWT (Stateless)Token assinado armazenado no cliente. Servidor valida assinatura sem consultar banco.APIs, microserviços, múltiplos servidores
Session (Stateful)ID de sessão no cookie. Estado armazenado no servidor (Redis/DB).Monolitos, quando precisa invalidar sessões
OAuth / OIDCDelegação de autenticação a terceiros (Google, GitHub, etc.)Login social, SSO corporativo

Auth.js (Next-Auth v5) — Implementação

auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
import { db } from "./lib/db";
import bcrypt from "bcryptjs";

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [
    GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! }),
    Credentials({
      async authorize(credentials) {
        const user = await db.user.findUnique({ where: { email: credentials.email as string } });
        if (!user?.password) return null;
        const ok = await bcrypt.compare(credentials.password as string, user.password);
        return ok ? user : null;
      },
    }),
  ],
  callbacks: {
    async session({ session, token }) {
      session.user.id   = token.sub!;
      session.user.role = token.role as string;
      return session;
    },
    async jwt({ token, user }) {
      if (user) token.role = (user as User).role;
      return token;
    },
  },
});

Segurança: O Checklist

// nunca faça isso
  • Nunca armazene senhas em texto plano — sempre bcrypt/argon2
  • Nunca confie em dados do cliente sem validação no servidor
  • Nunca exponha stack traces em produção
  • Nunca use eval() com dados do usuário
  • Nunca construa SQL com concatenação de strings (SQL injection)
VulnerabilidadePrevenção
SQL InjectionUse ORM (Prisma) ou prepared statements. Nunca concatene input do usuário em queries.
XSSReact escapa HTML por padrão. Nunca use dangerouslySetInnerHTML com input do usuário.
CSRFCORS correto, SameSite cookies, tokens CSRF para forms.
Rate LimitingLimite requests por IP/usuário com @upstash/ratelimit ou similar.
Secrets expostosUse .env + servidor de secrets (AWS SSM, Vault). Nunca commite .env.
IDORSempre verifique que o usuário autenticado tem acesso ao recurso requisitado.

CAP. 14 Avançado
Deploy & DevOps
CI/CD, containers e as plataformas que dominam o mercado

Plataformas por Caso de Uso

Vercel

Melhor para Next.js. Deploy via Git, edge functions, preview por PR. Free tier generoso.

🚂

Railway

Melhor para backends Node.js + banco de dados. Deploy de Docker ou via Git. Preço por uso.

🐳

Render

Alternativa ao Heroku. Web services, workers, cron jobs, banco PostgreSQL managed.

☁️

AWS / GCP / Azure

Para escala enterprise. Mais complexo mas máximo controle e infraestrutura.

Docker — Container do Backend

Dockerfile
# Multi-stage build — imagem final menor
FROM node:20-alpine AS base
RUN corepack enable pnpm

# Stage 1: instalar dependências
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Stage 2: build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build

# Stage 3: produção — imagem mínima
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
EXPOSE 3001
CMD ["node", "dist/server.js"]

GitHub Actions — CI/CD

.github/workflows/ci.yml
name: CI/CD

on:
  push: { branches: [main] }
  pull_request: { branches: [main] }

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: pnpm }

      - name: Install
        run: pnpm install --frozen-lockfile

      - name: Type check
        run: pnpm tsc --noEmit

      - name: Lint
        run: pnpm lint

      - name: Test
        run: pnpm test

      - name: Build
        run: pnpm build

  deploy:
    needs: check
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to Railway
        run: railway up
        env: { RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} }

CAP. 15 Avançado
Arquitetura & Patterns
Decisões de design que escalam — e as que não escalam

Estrutura de Pastas para Projetos Grandes

text
src/
├── app/                 ← Next.js App Router
│
├── components/
│   ├── ui/              ← shadcn/ui (primitivos)
│   ├── common/          ← compartilhados entre features
│   └── layouts/         ← Shell, Sidebar, Header
│
├── features/            ← organizado por domínio de negócio
│   ├── auth/
│   │   ├── components/  ← LoginForm, SignupModal
│   │   ├── hooks/       ← useAuth, useSession
│   │   ├── actions.ts   ← Server Actions
│   │   └── schema.ts    ← Zod schemas
│   ├── posts/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── api.ts       ← fetch functions
│   │   └── types.ts
│   └── dashboard/
│
├── lib/
│   ├── db.ts            ← Prisma client (singleton)
│   ├── auth.ts          ← Auth.js config
│   ├── utils.ts         ← cn(), formatDate(), etc.
│   └── validators.ts    ← schemas Zod reutilizáveis
│
├── hooks/               ← hooks globais (useDebounce, useLocalStorage)
├── store/               ← Zustand stores globais
└── types/               ← tipos compartilhados + .d.ts
// feature-first vs type-first
Organizar por feature/domínio (posts/, auth/, dashboard/) escala melhor do que organizar por tipo (components/, hooks/, utils/). No type-first, uma feature fica espalhada em 5 pastas diferentes. No feature-first, tudo relacionado a "posts" está em features/posts/.

O Stack Recomendado em 2025

CamadaFerramentaAlternativa
FrameworkNext.js 15Remix, Astro, Nuxt (Vue)
LinguagemTypeScript 5
EstiloTailwind CSS 4CSS Modules, Styled Components
UI Componentsshadcn/ui + RadixMantine, MUI
FormsReact Hook Form + ZodFormik, Conform
Server StateTanStack QuerySWR, RTK Query
Client StateZustandJotai, Redux Toolkit
AuthAuth.js v5Clerk, Lucia, Better Auth
ORMPrisma / DrizzleKysely, TypeORM
BancoPostgreSQLMySQL, SQLite (dev)
Deploy FrontendVercelNetlify, Cloudflare Pages
Deploy BackendRailway / RenderFly.io, AWS ECS
Package ManagerpnpmBun, npm, Yarn
TestesVitest + Testing LibraryJest, Playwright

Próximos Passos para Avançar

🧪

Testes

Vitest para unitários, Testing Library para componentes, Playwright para e2e. A pirâmide de testes aplicada a React.

📊

Observabilidade

Sentry para errors, Datadog/Grafana para métricas, OpenTelemetry para tracing distribuído.

Performance

Core Web Vitals (LCP, CLS, FID), Lighthouse, Web Workers, streaming com Suspense.

🏗️

Monorepos

Turborepo + pnpm workspaces para múltiplas apps compartilhando packages. Escala para times grandes.