---
url: 'https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql'
title: 'Aplicação CRUD com React, Node.js, Express e MySQL'
description: 'Este tutorial ensina como criar uma aplicação CRUD básica usando React no frontend e Express no backend com um banco de dados MySQL.'
lang: 'pt'
author: 'Lukas'
date: '2025-06-17T16:14:25.720Z'
lastModified: '2026-04-15T06:02:46.921Z'
keywords: 'CRUD, full-stack, tutorial, Sequelize, Axios, TailwindCSS'
category: 'Engineering'
---

# Aplicação CRUD com React, Node.js, Express e MySQL

Neste tutorial abrangente, você aprenderá a criar uma aplicação CRUD (Create, Read,
Update, Delete) full-stack usando [React](https://www.corbado.com/blog/react-passkeys) para o front-end e
[Node.js](https://www.corbado.com/blog/nodejs-passkeys) com Express para o back-end, tudo alimentado por um banco
de dados [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide). Utilizaremos tecnologias como
[React](https://www.corbado.com/blog/react-passkeys) Router, Axios e TailwindCSS para aprimorar a experiência do
usuário.

## 1. Visão Geral do Projeto

Desenvolveremos uma aplicação full-stack onde os usuários podem gerenciar tutoriais. Cada
tutorial terá os seguintes atributos:

- **ID**: Identificador único
- **Título**: Nome do tutorial
- **Descrição**: Informações detalhadas
- **Status de Publicação**: Indica se o tutorial está publicado

Os usuários podem realizar as seguintes ações:

- **Criar** um novo tutorial
- **Recuperar** todos os tutoriais ou um específico por ID
- **Atualizar** tutoriais existentes
- **Excluir** tutoriais
- **Pesquisar** tutoriais por título

A seguir, você encontrará algumas capturas de tela de exemplo:

### 1.1 Adicionar um Novo Tutorial

![Adicionar Item](https://www.corbado.com/website-assets/add_tutorial_370546a41c.png)

### 1.2 Exibir Todos os Tutoriais

![Mostrar Todos os Itens](https://www.corbado.com/website-assets/all_tutorials_c92482b796.png)

### 1.3 Editar um Tutorial

![Editar Item](https://www.corbado.com/website-assets/edit_tutorial_d108ded6a2.png)

### 1.4 Pesquisar Tutoriais por Título

![Pesquisar Tutoriais](https://www.corbado.com/website-assets/search_tutorial_6ae279cbd5.png)

## 2. Arquitetura

A aplicação segue uma arquitetura cliente-servidor:

- **Backend**: [Node.js](https://www.corbado.com/blog/nodejs-passkeys) com Express lida com APIs RESTful e
  interage com o banco de dados [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) usando o
  Sequelize ORM.
- **Frontend**: [React](https://www.corbado.com/blog/react-passkeys).js se comunica com o backend via Axios para
  requisições HTTP e usa o React Router para navegação.

![arquitetura react nodejs express mysql](https://www.corbado.com/website-assets/react_nodejs_express_mysql_architecture_65439de3a3.png)

## 3. Implementação do Backend

### 3.1 Configuração da Aplicação Node.js

1. **Crie o Diretório do Projeto:**

    ```bash
    mkdir react-node-express-mysql-crud
    cd react-node-express-mysql-crud
    ```

2. **Inicialize a Aplicação Node.js:**

    ```bash
    npm init -y
    ```

3. **Instale as Dependências:**

    ```bash
    npm install express sequelize mysql2 cors --save
    ```

4. **Use a Sintaxe ESModule** Adicione a seguinte linha ao seu arquivo package.json:
    ```json
    {
        "type": "module"
        // ...
    }
    ```

### 3.2 Configure o MySQL & Sequelize

1. **Crie o Arquivo de Configuração (`app/config/db.config.js`):**

```javascript
export default {
    HOST: "localhost",
    USER: "root",
    PASSWORD: "root",
    DB: "db",
    PORT: 8081,
    dialect: "mysql",
    pool: {
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000,
    },
};
```

2. **Inicialize o Sequelize (`app/models/index.js`):**

```javascript
import dbConfig from "../config/db.config.js";
import Sequelize from "sequelize";
import Tutorial from "./tutorial.model.js";

const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
    host: dbConfig.HOST,
    dialect: dbConfig.dialect,
    pool: dbConfig.pool,
    port: dbConfig.PORT,
});

const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.tutorials = Tutorial(sequelize, Sequelize);

export default db;
```

3. **Defina o Modelo do Tutorial (`app/models/tutorial.model.js`):**

```javascript
export default (sequelize, Sequelize) => {
    const Tutorial = sequelize.define("tutorial", {
        title: {
            type: Sequelize.STRING,
        },
        description: {
            type: Sequelize.STRING,
        },
        published: {
            type: Sequelize.BOOLEAN,
        },
    });
    return Tutorial;
};
```

Se você não tiver um banco de dados [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) para
desenvolvimento local, pode usar o Docker para criar um contêiner MySQL temporário:

```bash
docker run --rm -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_DATABASE=db \
  mysql:8
```

### 3.3 Defina Rotas e Controladores

1. **Crie o Controlador (`app/controllers/tutorial.controller.js`):**

```javascript
import db from "../models/index.js";

const Op = db.Sequelize.Op;
const Tutorial = db.tutorials;

// Cria e salva um novo Tutorial
export const create = (req, res) => {
    // Valida a requisição
    if (!req.body.title) {
        res.status(400).send({
            message: "O conteúdo não pode estar vazio!",
        });
        return;
    }

    // Cria um Tutorial
    const tutorial = {
        title: req.body.title,
        description: req.body.description,
        published: req.body.published ? req.body.published : false,
    };

    // Salva o Tutorial no banco de dados
    Tutorial.create(tutorial)
        .then((data) => {
            res.send(data);
        })
        .catch((err) => {
            res.status(500).send({
                message: err.message || "Ocorreu um erro ao criar o Tutorial.",
            });
        });
};

// Recupera todos os Tutoriais
export const findAll = (req, res) => {
    // Permite uma condição de filtro via parâmetro de consulta
    const title = req.query.title;
    const condition = title ? { title: { [Op.like]: `%${title}%` } } : null;

    Tutorial.findAll({ where: condition })
        .then((data) => {
            res.send(data);
        })
        .catch((err) => {
            res.status(500).send({
                message: err.message || "Ocorreu um erro ao recuperar os tutoriais.",
            });
        });
};

// Encontra um único Tutorial pelo ID
export const findOne = (req, res) => {
    const id = req.params.id;

    // Encontra o Tutorial pela chave primária
    Tutorial.findByPk(id)
        .then((data) => {
            if (data) {
                res.send(data);
            } else {
                res.status(404).send({
                    message: `Não foi possível encontrar o Tutorial com id=${id}.`,
                });
            }
        })
        .catch((err) => {
            res.status(500).send({
                message: "Erro ao recuperar o Tutorial com id=" + id,
            });
        });
};

// Atualiza um Tutorial pelo ID
export const update = (req, res) => {
    const id = req.params.id;

    // Atualiza o Tutorial com o ID especificado
    Tutorial.update(req.body, {
        where: { id: id },
    })
        .then((num) => {
            if (num == 1) {
                res.send({
                    message: "Tutorial foi atualizado com sucesso.",
                });
            } else {
                res.send({
                    message: `Não foi possível atualizar o Tutorial com id=${id}. Talvez o Tutorial não tenha sido encontrado ou req.body esteja vazio!`,
                });
            }
        })
        .catch((err) => {
            res.status(500).send({
                message: "Erro ao atualizar o Tutorial com id=" + id,
            });
        });
};

// Exclui um Tutorial pelo ID
export const deleteOne = (req, res) => {
    const id = req.params.id;

    // Exclui o Tutorial com o ID especificado
    Tutorial.destroy({
        where: { id: id },
    })
        .then((num) => {
            if (num == 1) {
                res.send({
                    message: "Tutorial foi excluído com sucesso!",
                });
            } else {
                res.send({
                    message: `Não foi possível excluir o Tutorial com id=${id}. Talvez o Tutorial não tenha sido encontrado!`,
                });
            }
        })
        .catch((err) => {
            res.status(500).send({
                message: "Não foi possível excluir o Tutorial com id=" + id,
            });
        });
};

// Exclui todos os Tutoriais
export const deleteAll = (req, res) => {
    // Exclui todos os Tutoriais
    Tutorial.destroy({
        where: {},
        truncate: false,
    })
        .then((nums) => {
            res.send({ message: `${nums} Tutoriais foram excluídos com sucesso!` });
        })
        .catch((err) => {
            res.status(500).send({
                message: err.message || "Ocorreu um erro ao remover todos os tutoriais.",
            });
        });
};

// Encontra todos os Tutoriais publicados
export const findAllPublished = (req, res) => {
    // Encontra todos os Tutoriais com published = true
    Tutorial.findAll({ where: { published: true } })
        .then((data) => {
            res.send(data);
        })
        .catch((err) => {
            res.status(500).send({
                message: err.message || "Ocorreu um erro ao recuperar os tutoriais.",
            });
        });
};
```

2. **Configure as Rotas (`app/routes/tutorial.routes.js`):**

```javascript
import * as tutorials from "../controllers/tutorial.controller.js";
import express from "express";

export default (app) => {
    let router = express.Router();

    // Cria um novo Tutorial
    router.post("/", tutorials.create);

    // Recupera todos os Tutoriais
    router.get("/", tutorials.findAll);

    // Recupera um único Tutorial com id
    router.get("/:id", tutorials.findOne);

    // Atualiza um Tutorial com id
    router.put("/:id", tutorials.update);

    // Exclui um Tutorial com id
    router.delete("/:id", tutorials.deleteOne);

    // Exclui todos os Tutoriais
    router.delete("/", tutorials.deleteAll);

    // Encontra todos os Tutoriais publicados
    router.get("/published", tutorials.findAllPublished);

    app.use("/api/tutorials", router);
};
```

### 3.4 Execute o Servidor

1. **Crie o Arquivo do Servidor (`server.js`):**

```javascript
import express from "express";
import cors from "cors";
import db from "./app/models/index.js";
import tutorialRoutes from "./app/routes/tutorial.routes.js";

const app = express();

const corsOptions = {
    origin: "http://localhost:5173",
};

app.use(cors(corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Rota simples
app.get("/", (req, res) => {
    res.json({ message: "Bem-vindo à Aplicação de Tutoriais." });
});

// Rotas
tutorialRoutes(app);

// Sincroniza o banco de dados
db.sequelize.sync().then(() => {
    console.log("Banco de dados sincronizado.");
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`Servidor está rodando na porta ${PORT}.`);
});
```

2. **Inicie o Servidor:**

```bash
node server.js
```

**Saída:**

```
Servidor está rodando na porta 8080.
Banco de dados sincronizado.
```

## 4. Implementação do Frontend

Esta é a arquitetura:

![arquitetura react](https://www.corbado.com/website-assets/react_architecture_bd0d482f67.png)

Alternativamente, você pode usar o Redux:

![arquitetura react redux](https://www.corbado.com/website-assets/react_redux_architecture_bf4a37be8a.png)

### 4.1 Estrutura de arquivos

Sua estrutura final de arquivos ficará assim:

```
frontend/
├─ index.html
├─ package.json
├─ postcss.config.js
├─ tailwind.config.js
├─ vite.config.js
├─ src/
│  ├─ App.jsx
│  ├─ main.jsx
│  ├─ index.css
│  ├─ services/
│  │   └─ tutorial.service.js
│  └─ pages/
│      ├─ AddTutorial.jsx
│      ├─ Tutorial.jsx
│      └─ TutorialsList.jsx
```

### 4.2 Crie a Aplicação React

Execute os seguintes comandos para configurar uma nova aplicação React usando o Vite:

```bash
npm create vite@latest frontend -- --template react
cd frontend
npm i
npm i react-router-dom axios
npm install -D tailwindcss autoprefixer
npx tailwindcss init -p
```

Finalmente, configure a opção de conteúdo do Tailwind no arquivo de configuração
`tailwind.config.js`:

```js
/** @type {import('tailwindcss').Config} */
export default {
    content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
    theme: {
        extend: {},
    },
    plugins: [],
};
```

Em seguida, abra [src](https://www.corbado.com/glossary/src)/index.css (o Vite criou este arquivo para você) e
adicione as diretivas do Tailwind:

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### 4.3 Inicialize o layout da Aplicação

O componente `App.jsx` configura as rotas do React Router e define uma barra de navegação
básica do Tailwind. Navegaremos entre:

- `/tutorials` – lista de tutoriais
- `/add` – formulário para criar novo tutorial
- `/tutorials/:id` – edição de um único tutorial

```jsx
import { Routes, Route, Link, BrowserRouter } from "react-router-dom";
import TutorialsList from "./pages/TutorialsList";
import AddTutorial from "./pages/AddTutorial";
import Tutorial from "./pages/Tutorial";

function App() {
    return (
        <BrowserRouter>
            <div>
                {/* BARRA DE NAVEGAÇÃO */}
                <nav className="bg-blue-600 p-4 text-white">
                    <div className="container mx-auto flex items-center justify-between">
                        <Link to="/" className="text-lg font-bold">
                            Corbado
                        </Link>
                        <div className="flex space-x-4">
                            <Link
                                to="/tutorials"
                                className="hover:text-gray-300 font-bold"
                            >
                                Tutoriais
                            </Link>
                            <Link to="/add" className="hover:text-gray-300">
                                Adicionar
                            </Link>
                        </div>
                    </div>
                </nav>

                {/* ROTAS */}
                <div className="container mx-auto mt-8 px-4">
                    <Routes>
                        <Route path="/" element={<TutorialsList />} />
                        <Route path="/tutorials" element={<TutorialsList />} />
                        <Route path="/add" element={<AddTutorial />} />
                        <Route path="/tutorials/:id" element={<Tutorial />} />
                    </Routes>
                </div>
            </div>
        </BrowserRouter>
    );
}

export default App;
```

### 4.4 Crie o Serviço de Dados

Este serviço lida com as requisições HTTP do Axios para o nosso backend Node/Express
([http://localhost:8080/api](http://localhost:8080/api)). Atualize a baseURL se o seu
servidor estiver rodando em um endereço ou porta diferente.

```js
import axios from "axios";

const http = axios.create({
    baseURL: "http://localhost:8080/api",
    headers: {
        "Content-Type": "application/json",
    },
});

const getAll = () => {
    return http.get("/tutorials");
};

const get = (id) => {
    return http.get(`/tutorials/${id}`);
};

const create = (data) => {
    return http.post("/tutorials", data);
};

const update = (id, data) => {
    return http.put(`/tutorials/${id}`, data);
};

const remove = (id) => {
    return http.delete(`/tutorials/${id}`);
};

const removeAll = () => {
    return http.delete("/tutorials");
};

const findByTitle = (title) => {
    return http.get(`/tutorials?title=${title}`);
};

export default {
    getAll,
    get,
    create,
    update,
    remove,
    removeAll,
    findByTitle,
};
```

### 4.5 Componente para Adicionar Item

Um componente para criar novos tutoriais em `src/pages/AddTutorial.jsx`. Ele permite
inserir título e descrição e, em seguida, chama `TutorialService.create()`.

```jsx
import React, { useState } from "react";
import TutorialService from "../services/tutorial.service";

function AddTutorial() {
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [submitted, setSubmitted] = useState(false);

    const saveTutorial = () => {
        const data = { title, description };
        TutorialService.create(data)
            .then((response) => {
                console.log(response.data);
                setSubmitted(true);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const newTutorial = () => {
        setTitle("");
        setDescription("");
        setSubmitted(false);
    };

    return (
        <div className="max-w-sm mx-auto p-4 bg-white rounded shadow">
            {submitted ? (
                <div>
                    <h4 className="font-bold text-green-600 mb-4">
                        Tutorial enviado com sucesso!
                    </h4>
                    <button
                        className="bg-blue-500 text-white px-3 py-1 rounded"
                        onClick={newTutorial}
                    >
                        Adicionar Outro
                    </button>
                </div>
            ) : (
                <div>
                    <h4 className="font-bold text-xl mb-2">Adicionar Tutorial</h4>

                    <div className="mb-2">
                        <label className="block mb-1 font-medium">Título</label>
                        <input
                            type="text"
                            className="border border-gray-300 rounded w-full px-2 py-1"
                            value={title}
                            onChange={(e) => setTitle(e.target.value)}
                        />
                    </div>

                    <div className="mb-2">
                        <label className="block mb-1 font-medium">Descrição</label>
                        <input
                            type="text"
                            className="border border-gray-300 rounded w-full px-2 py-1"
                            value={description}
                            onChange={(e) => setDescription(e.target.value)}
                        />
                    </div>

                    <button
                        className="bg-green-500 text-white px-3 py-1 rounded mt-2"
                        onClick={saveTutorial}
                    >
                        Enviar
                    </button>
                </div>
            )}
        </div>
    );
}

export default AddTutorial;
```

### 4.6 Componente de Lista de Tutoriais

Um componente em `src/pages/TutorialsList.jsx` que:

- Exibe uma barra de pesquisa para filtrar por título do tutorial
- Lista os tutoriais à esquerda
- Mostra o tutorial selecionado à direita
- Fornece um botão para remover todos os tutoriais

```jsx
import { useState, useEffect } from "react";
import TutorialService from "../services/tutorial.service";
import { Link } from "react-router-dom";

function TutorialsList() {
    const [tutorials, setTutorials] = useState([]);
    const [currentTutorial, setCurrentTutorial] = useState(null);
    const [currentIndex, setCurrentIndex] = useState(-1);
    const [searchTitle, setSearchTitle] = useState("");

    useEffect(() => {
        retrieveTutorials();
    }, []);

    const onChangeSearchTitle = (e) => {
        setSearchTitle(e.target.value);
    };

    const retrieveTutorials = () => {
        TutorialService.getAll()
            .then((response) => {
                setTutorials(response.data);
                console.log(response.data);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const refreshList = () => {
        retrieveTutorials();
        setCurrentTutorial(null);
        setCurrentIndex(-1);
    };

    const setActiveTutorial = (tutorial, index) => {
        setCurrentTutorial(tutorial);
        setCurrentIndex(index);
    };

    const removeAllTutorials = () => {
        TutorialService.removeAll()
            .then((response) => {
                console.log(response.data);
                refreshList();
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const findByTitle = () => {
        TutorialService.findByTitle(searchTitle)
            .then((response) => {
                setTutorials(response.data);
                setCurrentTutorial(null);
                setCurrentIndex(-1);
                console.log(response.data);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    return (
        <div className="flex flex-col lg:flex-row gap-8">
            {/* COLUNA ESQUERDA: PESQUISA + LISTA */}
            <div className="flex-1">
                <div className="flex mb-4">
                    <input
                        type="text"
                        className="border border-gray-300 rounded-l px-2 py-1 w-full"
                        placeholder="Pesquisar por título"
                        value={searchTitle}
                        onChange={onChangeSearchTitle}
                    />
                    <button
                        className="bg-blue-500 text-white px-4 py-1 rounded-r"
                        onClick={findByTitle}
                    >
                        Pesquisar
                    </button>
                </div>

                <h4 className="font-bold text-lg mb-2">Lista de Tutoriais</h4>
                <ul className="divide-y divide-gray-200 border border-gray-200 rounded">
                    {tutorials &&
                        tutorials.map((tutorial, index) => (
                            <li
                                className={
                                    "px-4 py-2 cursor-pointer " +
                                    (index === currentIndex ? "bg-blue-100" : "")
                                }
                                onClick={() => setActiveTutorial(tutorial, index)}
                                key={index}
                            >
                                {tutorial.title}
                            </li>
                        ))}
                </ul>

                <button
                    className="bg-red-500 text-white px-3 py-1 rounded mt-4"
                    onClick={removeAllTutorials}
                >
                    Remover Todos
                </button>
            </div>

            {/* COLUNA DIREITA: DETALHES */}
            <div className="flex-1">
                {currentTutorial ? (
                    <div className="p-4 bg-white rounded shadow">
                        <h4 className="font-bold text-xl mb-2">Tutorial</h4>
                        <div className="mb-2">
                            <strong>Título: </strong>
                            {currentTutorial.title}
                        </div>
                        <div className="mb-2">
                            <strong>Descrição: </strong>
                            {currentTutorial.description}
                        </div>
                        <div className="mb-2">
                            <strong>Status: </strong>
                            {currentTutorial.published ? "Publicado" : "Pendente"}
                        </div>

                        <Link
                            to={`/tutorials/${currentTutorial.id}`}
                            className="inline-block bg-yellow-400 text-black px-3 py-1 rounded"
                        >
                            Editar
                        </Link>
                    </div>
                ) : (
                    <div>
                        <p>Por favor, clique em um Tutorial...</p>
                    </div>
                )}
            </div>
        </div>
    );
}

export default TutorialsList;
```

### 4.7 Componente do Tutorial

Um componente funcional em `src/pages/Tutorial.jsx` para visualizar e editar um único
tutorial. Ele usa:

- `useParams()` para obter :id da URL
- `useNavigate()` para redirecionar
- `TutorialService` para operações de get, update e delete

```jsx
import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import TutorialService from "../services/tutorial.service";

function Tutorial() {
    const { id } = useParams();
    const navigate = useNavigate();

    const [currentTutorial, setCurrentTutorial] = useState({
        id: null,
        title: "",
        description: "",
        published: false,
    });
    const [message, setMessage] = useState("");

    const getTutorial = (id) => {
        TutorialService.get(id)
            .then((response) => {
                setCurrentTutorial(response.data);
                console.log(response.data);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    useEffect(() => {
        if (id) getTutorial(id);
    }, [id]);

    const handleInputChange = (event) => {
        const { name, value } = event.target;
        setCurrentTutorial({ ...currentTutorial, [name]: value });
    };

    const updatePublished = (status) => {
        const data = {
            ...currentTutorial,
            published: status,
        };

        TutorialService.update(currentTutorial.id, data)
            .then((response) => {
                setCurrentTutorial({ ...currentTutorial, published: status });
                console.log(response.data);
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const updateTutorial = () => {
        TutorialService.update(currentTutorial.id, currentTutorial)
            .then((response) => {
                console.log(response.data);
                setMessage("O tutorial foi atualizado com sucesso!");
            })
            .catch((e) => {
                console.log(e);
            });
    };

    const deleteTutorial = () => {
        TutorialService.remove(currentTutorial.id)
            .then((response) => {
                console.log(response.data);
                navigate("/tutorials");
            })
            .catch((e) => {
                console.log(e);
            });
    };

    return (
        <div>
            {currentTutorial ? (
                <div className="max-w-sm mx-auto p-4 bg-white rounded shadow">
                    <h4 className="font-bold text-xl mb-2">Editar Tutorial</h4>
                    <div className="mb-2">
                        <label className="block font-medium" htmlFor="title">
                            Título
                        </label>
                        <input
                            type="text"
                            className="border border-gray-300 rounded w-full px-2 py-1"
                            id="title"
                            name="title"
                            value={currentTutorial.title}
                            onChange={handleInputChange}
                        />
                    </div>

                    <div className="mb-2">
                        <label className="block font-medium" htmlFor="description">
                            Descrição
                        </label>
                        <input
                            type="text"
                            className="border border-gray-300 rounded w-full px-2 py-1"
                            id="description"
                            name="description"
                            value={currentTutorial.description}
                            onChange={handleInputChange}
                        />
                    </div>

                    <div className="mb-2">
                        <strong>Status:</strong>{" "}
                        {currentTutorial.published ? "Publicado" : "Pendente"}
                    </div>

                    <div className="space-x-2 mt-2">
                        {currentTutorial.published ? (
                            <button
                                className="bg-blue-500 text-white px-3 py-1 rounded"
                                onClick={() => updatePublished(false)}
                            >
                                Despublicar
                            </button>
                        ) : (
                            <button
                                className="bg-blue-500 text-white px-3 py-1 rounded"
                                onClick={() => updatePublished(true)}
                            >
                                Publicar
                            </button>
                        )}

                        <button
                            className="bg-red-500 text-white px-3 py-1 rounded"
                            onClick={deleteTutorial}
                        >
                            Excluir
                        </button>

                        <button
                            className="bg-green-500 text-white px-3 py-1 rounded"
                            onClick={updateTutorial}
                        >
                            Atualizar
                        </button>
                    </div>

                    {message && <p className="text-green-600 mt-2">{message}</p>}
                </div>
            ) : (
                <div>
                    <p>Carregando tutorial...</p>
                </div>
            )}
        </div>
    );
}

export default Tutorial;
```

### 4.8 Execute a aplicação

```bash
npm run dev
```

Agora você pode acessar a aplicação em [http://localhost:5173](http://localhost:5173).
Abra essa URL no seu navegador. Você pode navegar para:

- `/tutorials` – ver todos os tutoriais
- `/add` – adicionar um tutorial
- `/tutorials/:id` – editar um tutorial específico

(Certifique-se de que seu back-end Node/Express esteja rodando em
[http://localhost:8080](http://localhost:8080) ou atualize a baseURL em
tutorial.service.js de acordo.)

## 5. Conclusão

Você construiu com sucesso uma aplicação CRUD full-stack usando React,
[Node.js](https://www.corbado.com/blog/nodejs-passkeys), Express e MySQL. Este projeto demonstra como configurar
uma API RESTful com Node.js e Express, gerenciar dados com o Sequelize ORM e criar um
frontend responsivo com React e TailwindCSS.

Bom código e até o próximo tutorial!
