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? 🎯