PV

Solid Javascript developer

mente • 24/04/2026

Vou te dar um guia completo para se tornar um fullstack profissional! 🚀

📚 1. SOLID Principles em JavaScript/Next.js

S - Single Responsibility Principle (Responsabilidade Única)

Cada componente/função deve ter apenas uma responsabilidade.

// ❌ ERRADO - Componente faz muitas coisas
function UserProfile() {
  const [user, setUser] = useState(null);
  
  // Busca dados
  useEffect(() => {
    fetch('/api/user').then(r => r.json()).then(setUser);
  }, []);
  
  // Valida dados
  const validateUser = (data) => { /* validação */ };
  
  // Formata dados
  const formatDate = (date) => { /* formatação */ };
  
  return <div>{/* UI */}</div>;
}

// ✅ CORRETO - Separar responsabilidades
// hooks/useUser.js
function useUser() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetch('/api/user').then(r => r.json()).then(setUser);
  }, []);
  
  return user;
}

// utils/userValidator.js
export const validateUser = (data) => { /* validação */ };

// utils/dateFormatter.js
export const formatDate = (date) => { /* formatação */ };

// components/UserProfile.jsx
function UserProfile() {
  const user = useUser();
  
  if (!user) return <Loading />;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{formatDate(user.createdAt)}</p>
    </div>
  );
}

O - Open/Closed Principle (Aberto/Fechado)

Aberto para extensão, fechado para modificação.

// ❌ ERRADO - Precisa modificar o código para adicionar novos tipos
function Button({ type, children }) {
  if (type === 'primary') {
    return <button className="bg-blue-600">{children}</button>;
  }
  if (type === 'secondary') {
    return <button className="bg-gray-600">{children}</button>;
  }
  // Sempre precisa adicionar mais ifs...
}

// ✅ CORRETO - Extensível sem modificar
const buttonStyles = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  secondary: 'bg-gray-600 text-white hover:bg-gray-700',
  danger: 'bg-red-600 text-white hover:bg-red-700',
  success: 'bg-green-600 text-white hover:bg-green-700'
};

function Button({ variant = 'primary', children, className = '' }) {
  return (
    <button className={`px-4 py-2 rounded ${buttonStyles[variant]} ${className}`}>
      {children}
    </button>
  );
}

// Fácil adicionar novos estilos sem modificar o componente

L - Liskov Substitution Principle (Substituição de Liskov)

Subtipos devem ser substituíveis por seus tipos base.

// ❌ ERRADO - TextInput não pode substituir Input completamente
class Input {
  validate() { return true; }
}

class TextInput extends Input {
  validate() { throw new Error('Not implemented'); } // Quebra o contrato!
}

// ✅ CORRETO - Todos os inputs seguem o mesmo contrato
// components/inputs/BaseInput.jsx
export function BaseInput({ value, onChange, validate, ...props }) {
  return <input value={value} onChange={onChange} {...props} />;
}

// components/inputs/TextInput.jsx
export function TextInput(props) {
  const validate = (value) => value.length > 0;
  return <BaseInput {...props} validate={validate} />;
}

// components/inputs/EmailInput.jsx
export function EmailInput(props) {
  const validate = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
  return <BaseInput {...props} validate={validate} />;
}

// Todos podem ser usados da mesma forma
function Form() {
  return (
    <>
      <TextInput value={name} onChange={setName} />
      <EmailInput value={email} onChange={setEmail} />
    </>
  );
}

I - Interface Segregation Principle (Segregação de Interface)

Clientes não devem depender de interfaces que não usam.

// ❌ ERRADO - Card precisa implementar todas as props mesmo não usando
function Card({ title, content, image, video, audio, onEdit, onDelete, onShare, onLike }) {
  // Card simples não precisa de todas essas props
  return <div>{title}</div>;
}

// ✅ CORRETO - Componentes específicos para cada necessidade
// components/cards/SimpleCard.jsx
function SimpleCard({ title, content }) {
  return (
    <div className="p-4 border rounded">
      <h3>{title}</h3>
      <p>{content}</p>
    </div>
  );
}

// components/cards/MediaCard.jsx
function MediaCard({ title, content, image }) {
  return (
    <div className="p-4 border rounded">
      {image && <img src={image} alt={title} />}
      <h3>{title}</h3>
      <p>{content}</p>
    </div>
  );
}

// components/cards/InteractiveCard.jsx
function InteractiveCard({ title, content, onEdit, onDelete }) {
  return (
    <div className="p-4 border rounded">
      <h3>{title}</h3>
      <p>{content}</p>
      <button onClick={onEdit}>Edit</button>
      <button onClick={onDelete}>Delete</button>
    </div>
  );
}

D - Dependency Inversion Principle (Inversão de Dependência)

Dependa de abstrações, não de implementações concretas.

// ❌ ERRADO - Componente depende diretamente da implementação do fetch
function UserList() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch('/api/users')
      .then(r => r.json())
      .then(setUsers);
  }, []);
  
  return users.map(u => <div>{u.name}</div>);
}

// ✅ CORRETO - Componente depende de abstração (hook)
// services/api.js
export const apiService = {
  getUsers: () => fetch('/api/users').then(r => r.json()),
  getUser: (id) => fetch(`/api/users/${id}`).then(r => r.json()),
};

// hooks/useUsers.js
export function useUsers() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    apiService.getUsers()
      .then(setUsers)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);
  
  return { users, loading, error };
}

// components/UserList.jsx
function UserList() {
  const { users, loading, error } = useUsers();
  
  if (loading) return <Loading />;
  if (error) return <Error message={error.message} />;
  
  return users.map(u => <UserCard key={u.id} user={u} />);
}

🏗️ 2. Estrutura de Projeto Profissional

meu-projeto/
├── frontend/ (Next.js)
│   ├── src/
│   │   ├── app/                    # Next.js 13+ App Router
│   │   │   ├── page.jsx           # Página principal
│   │   │   ├── layout.jsx         # Layout raiz
│   │   │   ├── dashboard/
│   │   │   │   └── page.jsx
│   │   │   └── api/               # API Routes (opcional)
│   │   │
│   │   ├── components/            # Componentes reutilizáveis
│   │   │   ├── ui/                # Componentes base (Button, Input, etc)
│   │   │   │   ├── Button.jsx
│   │   │   │   ├── Input.jsx
│   │   │   │   └── Card.jsx
│   │   │   ├── layout/            # Componentes de layout
│   │   │   │   ├── Header.jsx
│   │   │   │   ├── Sidebar.jsx
│   │   │   │   └── Footer.jsx
│   │   │   └── features/          # Componentes específicos
│   │   │       ├── UserProfile/
│   │   │       ├── ProjectList/
│   │   │       └── ItemEditor/
│   │   │
│   │   ├── hooks/                 # Custom hooks
│   │   │   ├── useUser.js
│   │   │   ├── useProjects.js
│   │   │   └── useLocalStorage.js
│   │   │
│   │   ├── services/              # Serviços de API
│   │   │   ├── api.js             # Configuração base do Axios
│   │   │   ├── userService.js
│   │   │   ├── projectService.js
│   │   │   └── cache.js           # localStorage cache
│   │   │
│   │   ├── utils/                 # Funções utilitárias
│   │   │   ├── formatters.js
│   │   │   ├── validators.js
│   │   │   └── constants.js
│   │   │
│   │   ├── types/                 # TypeScript types (se usar TS)
│   │   │   └── index.ts
│   │   │
│   │   └── styles/                # Estilos globais
│   │       └── globals.css
│   │
│   ├── public/                    # Arquivos estáticos
│   └── package.json
│
└── backend/ (Flask)
    ├── app/
    │   ├── __init__.py
    │   ├── routes/
    │   │   ├── __init__.py
    │   │   ├── users.py
    │   │   └── projects.py
    │   ├── models/
    │   │   ├── __init__.py
    │   │   ├── user.py
    │   │   └── project.py
    │   ├── services/
    │   │   ├── __init__.py
    │   │   ├── user_service.py
    │   │   └── project_service.py
    │   └── utils/
    │       ├── __init__.py
    │       └── validators.py
    ├── config.py
    ├── run.py
    └── requirements.txt

🔄 3. Migração de HTML para Next.js (Passo a Passo)

Exemplo Prático:

<!-- Seu HTML atual: dashboard.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Dashboard</title>
    <style>
        .card { padding: 20px; border: 1px solid #ccc; }
        .card-title { font-size: 24px; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            <h1 class="card-title">Dashboard</h1>
            <p id="user-name"></p>
        </div>
    </div>
    
    <script>
        fetch('/api/user')
            .then(r => r.json())
            .then(user => {
                document.getElementById('user-name').textContent = user.name;
            });
    </script>
</body>
</html>
// Conversão para Next.js: app/dashboard/page.jsx
'use client';

import { useEffect, useState } from 'react';
import styles from './dashboard.module.css';

export default function DashboardPage() {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetch('/api/user')
      .then(r => r.json())
      .then(setUser);
  }, []);
  
  return (
    <div className={styles.container}>
      <div className={styles.card}>
        <h1 className={styles.cardTitle}>Dashboard</h1>
        <p>{user?.name}</p>
      </div>
    </div>
  );
}
/* dashboard.module.css */
.container {
  padding: 20px;
}

.card {
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
}

.cardTitle {
  font-size: 24px;
  font-weight: bold;
}

🎯 4. Flask + Next.js + localStorage (Arquitetura Profissional)

Backend Flask:

# backend/app/__init__.py
from flask import Flask
from flask_cors import CORS

def create_app():
    app = Flask(__name__)
    CORS(app)
    
    from app.routes import users, projects
    app.register_blueprint(users.bp)
    app.register_blueprint(projects.bp)
    
    return app

# backend/app/routes/projects.py
from flask import Blueprint, jsonify, request

bp = Blueprint('projects', __name__, url_prefix='/api/projects')

@bp.route('/', methods=['GET'])
def get_projects():
    # Lógica para buscar projetos
    projects = [
        {'id': 1, 'title': 'Projeto 1', 'status': 'active'},
        {'id': 2, 'title': 'Projeto 2', 'status': 'completed'}
    ]
    return jsonify(projects)

@bp.route('/', methods=['POST'])
def create_project():
    data = request.json
    # Lógica para criar projeto
    return jsonify({'id': 3, **data}), 201

@bp.route('/<int:project_id>', methods=['PUT'])
def update_project(project_id):
    data = request.json
    # Lógica para atualizar projeto
    return jsonify({'id': project_id, **data})

@bp.route('/<int:project_id>', methods=['DELETE'])
def delete_project(project_id):
    # Lógica para deletar projeto
    return '', 204

Frontend Next.js com Cache:

// services/api.js
import axios from 'axios';

const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000/api'
});

export default api;

// services/cache.js
export const cache = {
  get: (key) => {
    try {
      const item = localStorage.getItem(key);
      if (!item) return null;
      
      const { data, timestamp, ttl } = JSON.parse(item);
      
      // Verifica se expirou
      if (Date.now() - timestamp > ttl) {
        localStorage.removeItem(key);
        return null;
      }
      
      return data;
    } catch {
      return null;
    }
  },
  
  set: (key, data, ttl = 300000) => { // 5 minutos padrão
    try {
      const item = {
        data,
        timestamp: Date.now(),
        ttl
      };
      localStorage.setItem(key, JSON.stringify(item));
    } catch (error) {
      console.error('Error setting cache:', error);
    }
  },
  
  remove: (key) => {
    localStorage.removeItem(key);
  },
  
  clear: () => {
    localStorage.clear();
  }
};

// services/projectService.js
import api from './api';
import { cache } from './cache';

export const projectService = {
  async getAll() {
    const cacheKey = 'projects';
    
    // Tenta buscar do cache primeiro
    const cached = cache.get(cacheKey);
    if (cached) {
      console.log('📦 Usando cache');
      return cached;
    }
    
    // Se não tem cache, busca da API
    console.log('🌐 Buscando da API');
    const { data } = await api.get('/projects');
    
    // Salva no cache
    cache.set(cacheKey, data);
    
    return data;
  },
  
  async getById(id) {
    const cacheKey = `project_${id}`;
    const cached = cache.get(cacheKey);
    if (cached) return cached;
    
    const { data } = await api.get(`/projects/${id}`);
    cache.set(cacheKey, data);
    return data;
  },
  
  async create(project) {
    const { data } = await api.post('/projects', project);
    
    // Invalida cache da lista
    cache.remove('projects');
    
    return data;
  },
  
  async update(id, project) {
    const { data } = await api.put(`/projects/${id}`, project);
    
    // Invalida caches
    cache.remove('projects');
    cache.remove(`project_${id}`);
    
    return data;
  },
  
  async delete(id) {
    await api.delete(`/projects/${id}`);
    
    // Invalida caches
    cache.remove('projects');
    cache.remove(`project_${id}`);
  }
};

// hooks/useProjects.js
import { useState, useEffect } from 'react';
import { projectService } from '@/services/projectService';

export function useProjects() {
  const [projects, setProjects] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  const fetchProjects = async () => {
    try {
      setLoading(true);
      const data = await projectService.getAll();
      setProjects(data);
    } catch (err) {
      setError(err);
    } finally {
      setLoading(false);
    }
  };
  
  useEffect(() => {
    fetchProjects();
  }, []);
  
  const addProject = async (project) => {
    const newProject = await projectService.create(project);
    setProjects(prev => [...prev, newProject]);
    return newProject;
  };
  
  const updateProject = async (id, updates) => {
    const updated = await projectService.update(id, updates);
    setProjects(prev => prev.map(p => p.id === id ? updated : p));
    return updated;
  };
  
  const deleteProject = async (id) => {
    await projectService.delete(id);
    setProjects(prev => prev.filter(p => p.id !== id));
  };
  
  return {
    projects,
    loading,
    error,
    addProject,
    updateProject,
    deleteProject,
    refetch: fetchProjects
  };
}

// components/ProjectList.jsx
'use client';

import { useProjects } from '@/hooks/useProjects';

export default function ProjectList() {
  const { projects, loading, error, deleteProject } = useProjects();
  
  if (loading) return <div>Carregando...</div>;
  if (error) return <div>Erro: {error.message}</div>;
  
  return (
    <div>
      <h1>Projetos</h1>
      {projects.map(project => (
        <div key={project.id}>
          <h2>{project.title}</h2>
          <button onClick={() => deleteProject(project.id)}>
            Excluir
          </button>
        </div>
      ))}
    </div>
  );
}

🚀 5. Roadmap para Fullstack Profissional

Fase 1: Fundamentos (1-2 meses)

  • ✅ JavaScript ES6+ (async/await, destructuring, spread, etc)
  • ✅ React Hooks profundamente
  • ✅ Next.js App Router
  • ✅ Flask REST APIs
  • ✅ SQL/PostgreSQL básico

Fase 2: Intermediário (2-3 meses)

  • ✅ SOLID principles na prática
  • ✅ TypeScript
  • ✅ State Management (Zustand ou Context API)
  • ✅ Testes (Jest, React Testing Library)
  • ✅ Flask + SQLAlchemy ORM
  • ✅ Autenticação JWT

Fase 3: Avançado (3-4 meses)

  • ✅ Arquitetura limpa (Clean Architecture)
  • ✅ Design Patterns
  • ✅ Performance optimization
  • ✅ Docker
  • ✅ CI/CD
  • ✅ Deploy (Vercel + Railway/Heroku)

Fase 4: Expert (ongoing)

  • ✅ Microsserviços
  • ✅ GraphQL
  • ✅ WebSockets
  • ✅ Monitoramento e logs
  • ✅ Segurança avançada

Quer que eu crie exemplos práticos de algum desses tópicos específicos? 🎯