Skip to content

"This project is a Star Wars-themed CRUD API exercise designed to help developers practice building RESTful endpoints, applying validation and error handling, and interacting with a MongoDB database using Mongoose. It serves as a fun and educational way to reinforce backend development skills in a structured, real-world scenario."

Notifications You must be signed in to change notification settings

TVATDCI/Star-Wars-Character-Data-API

Repository files navigation

Star Wars Character Database CRUD API

GitHub Forks GitHub Issues GitHub Last Commit GitHub Stars

Algorithm Challenge

"This Star Wars-themed API project is a full-stack backend exercise crafted to reinforce key skills in RESTful API development. Using Express.js and MongoDB with Mongoose, it allows developers to practice creating and testing CRUD operations, implementing validation, error handling, and working with dynamic routes. It's an engaging, hands-on way to apply theoretical knowledge in a themed and memorable context."

Visitors

Custom Label


🔱May the Fork Be With You

Table of Contents

Project Overview

Create a REST API that manages a database of Star Wars characters. Users should be able to create, read, update, and delete character information through various endpoints.


Project Structure

project-root/
├── backend/
│ ├── config/
│ │ └── db.js
│ ├── controller/
│ │ └── characterController.js
│ ├── libs/
│ │ ├── seeds.js
│ ├── middleware/
│ │ ├── authMiddleware.js
│ │ └── requireAdmin.js
│ ├── models/
│ │ ├── characterModel.js
│ │ └── userModel.js
│ ├── routes/
│ │ ├── characterRoutes.js
│ │ ├── publicRoutes.js
│ │ └── userProfile.js
│ ├── app.js
│ └── package.json
├── frontend/
│ ├── public/
│ ├── src/
│ │ ├── components/
│ │ │ ├── buttons/
│ │ │ │   ├── BtnNeonGradient.jsx
│ │ │ │   ├── Button.jsx
│ │ │ │   ├── ButtonGradient.jsx
│ │ │ │   ├── ButtonSvg.jsx
│ │ │ │   ├── SpaceBtn.jsx
│ │ │ │   └── SpaceBtnSvg.jsx
│ │ │ ├── form/
│ │ │ │   ├── ArrayInput.jsx
│ │ │ │   ├── CheckboxInput.jsx
│ │ │ │   ├── NumberInput.jsx
│ │ │ │   └── TextInput.jsx
│ │ │ ├── hooks/
│ │ │ │   └── UserProfileFetches.js
│ │ │ ├── reg-auth/
│ │ │ │   ├── LoginForm.jsx
│ │ │ │   └── RegisterForm.jsx
│ │ │ ├── spaceAtmos/
│ │ │ │   ├── NebulaCanvas.jsx
│ │ │ │   ├── nebulaCloud.js
│ │ │ │   └── star.js
│ │ │ └── utils/
│ │ │ │   ├── api.js
│ │ │ │   ├── auth.js
│ │ │ │   └── debounce.js
│ │ │ └── views/
│ │ │ │   ├── InfoPage.jsx
│ │ │ │   └── UserProfile.jsx
│ │ │ ├── CharacterDetail.jsx
│ │ │ ├── Characters.jsx
│ │ │ └── CharacterForm.jsx
│ │ │ └── ViewRouter.jsx
│ │ ├── context/
│ │ │ ├── AppContext.jsx
│ │ ├── App.jsx
│ │ ├── App.css
│ │ └── index.css
│ ├── main.jsx
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ └── tailwind.config.js
├── .gitignore
├── README.md

Async/Await vs .then/.catch

Async/Await

  • Syntax: Cleaner and more readable, especially for complex logic.
  • Error Handling: Use try/catch blocks to handle errors.
  • Sequential Execution: Makes asynchronous code look synchronous, which is easier to follow.
  • Example: **Example
    const handleSubmit = async (e) => {
      e.preventDefault();
      try {
        const response = await fetch("http://localhost:5000/login", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ email, password }),
        });
        const data = await response.json();
        if (response.ok) {
          console.log("Token received:", data.token);
          localStorage.setItem("token", data.token);
        } else {
          console.error("Login failed:", data.error);
        }
      } catch (error) {
        console.error("Error during login:", error);
      }
    };

### .then/.catch

- **Syntax**: Uses chained `.then()` for success and `.catch()` for errors.
- **Error Handling**: Errors are caught in the `.catch()` block.
- **Readability**: Can become harder to read with nested `.then()` calls (callback hell).

**Example**:

```javascript
  const handleSubmit = (e) => {
    e.preventDefault();
    fetch("http://localhost:5000/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    })
      .then((response) => response.json())
      .then((data) => {
        if (data.token) {
          console.log("Token received:", data.token);
          localStorage.setItem("token", data.token);
        } else {
          console.error("Login failed:", data.error);
        }
      })
      .catch((error) => {
        console.error("Error during login:", error);
      });
  };

Key Differences

Feature async/await .then/.catch
Syntax Cleaner and more readable Can become verbose with chaining
Error Handling try/catch blocks .catch() method
Readability Easier for sequential logic Harder with nested .then() calls
Use Case Preferred for modern JavaScript Useful for simple, quick operations

Logout Functionality Recap

  • In this single-page application (SPA) setup controlled by setView, there's no functional need to create a separate LogoutForm.jsx
  • Unless you're trying to keep your code modular or plan to reuse the logout UI. So, No, LogoutForm.jsx

Frontend Logout:

  • The handleLogout function in App.jsx removes the token from localStorage and resets the user state.

Example:

const handleLogout = () => {
  console.log("Logging out...");
  setUser(null);
  localStorage.removeItem("token");
  localStorage.removeItem("userEmail");
  localStorage.removeItem("userRole"); // Remove userRole as well
  console.log("Token, userEmail and userRole removed from localStorage");
  setView("info");
};

<button className="style of your choice" onClick={handleLogout}>
  Logout
</button>;

Backend /logout:

  • Currently, there is no /logout route in the backend. This is fine for a stateless JWT-based authentication system.

  • If needed, a /logout route could simply return a success message:

    app.post("/logout", (req, res) => {
      res.json({ message: "Logged out successfully" });
    });

  1. Implement Bearer Token Logic:

    • Protect routes like /api/characters by requiring a valid token in the Authorization header.

    • Example middleware:

      const authenticateToken = (req, res, next) => {
        const token = req.headers["authorization"]?.split(" ")[1];
        if (!token) return res.status(401).json({ error: "Access denied" });
      
        jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
          if (err) return res.status(403).json({ error: "Invalid token" });
          req.user = user;
          next();
        });
      };
  2. Frontend Token Usage:

    • Include the token in the Authorization header for authenticated requests:
      const token = localStorage.getItem("token");
      fetch("http://localhost:5000/protected-route", {
        method: "GET",
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });
  3. Enhance Logout:

    • Ensure the token is removed from localStorage and the app state is reset.

  1. Optional Backend /logout:

    • If you want to add a /logout route, it could simply return a success message. For example:
      app.post("/logout", (req, res) => {
        res.json({ message: "Logged out successfully" });
      });
    • However, this is not strictly necessary unless you want to implement token invalidation.
  2. Protected Routes:

    • Bearer token logic to protect routes like /api/characters. This ensures only authenticated users can access certain endpoints.
    • Implement Bearer token logic in the backend to protect routes.
    • Update the frontend to include the token in the Authorization header for authenticated requests.
  3. Recap of Current Flow:

    • Register: User registers via /register and gets added to the database.
    • Login: User logs in via /login, receives a JWT token, and stores it in localStorage
    • Logout: User clicks the "Logout" button, which removes the token from localStorage and resets the app state.

Extra recap

JWT in Header vs HTTP-Only Cookie Authentication

When building authentication systems, you can store the JWT token in two main ways: HTTP-only cookies or in the Authorization header (Bearer token).

1. JWT in Authorization Header (Current Approach)

  • Storage: The token is saved in localStorage or sessionStorage.

  • How It Works: Sent via the Authorization header with every request.

  • Example:

    fetch("http://localhost:5000/api/characters", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
  • Pros:

    • Easy to implement.
    • Works well for APIs and SPA setups like React apps.
  • Cons:

    • Vulnerable to XSS attacks if your site is not secure.
    • Developers must manually include the token in each request.

2. JWT in HTTP-Only Cookies (More Secure)

  • Storage: Stored in a cookie with HttpOnly and Secure flags.
  • How It Works: Automatically sent by the browser with every request to your backend (no manual Authorization header needed).

Set by Backend:

res.cookie("token", jwtToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === "production",
  sameSite: "Strict",
  maxAge: 3600000, // 1 hour
});

Pros:

  • Immune to XSS (JavaScript cannot access the cookie).
  • Safer for storing sensitive tokens.

Cons:

  • Slightly more complex to implement.
  • CSRF protection needs to be considered.

Developer Notes – Creating Admin Users

In production, all users are registered with the default "user" role. Admin accounts should never be exposed via the frontend form.

Creating an Admin User via Postman (Development Only)

To register an admin user during development:

  • 1.Open Postman or similar API tool.
  • 2.Send a POST request to:
http://localhost:5000/register
  • 3.Use the following request body:**
{
  "email": "[email protected]",
  "password": "yourSecurePassword",
  "role": "admin"
}
  • 4.If successful, the response should look like:
{
  "message": "User registered successfully"
}

This method allows you to test requireAdmin middleware and admin-only routes securely in development.

Frontend Behavior

RegisterForm.jsx does not expose the ability to set the user role.

  • All users registering through the frontend will always be assigned the "user" role by default, as enforced by the backend.

⚠️ Giving clients access to register as admin in production would be like handing out lightsabers or Death Star control panels to Jawas — don't!

$$ {\color{red}Made \space \color{red} With \space \color{red}❤️ \space \color{gold}and \space \color{lightgreen}Headache } $$


About

"This project is a Star Wars-themed CRUD API exercise designed to help developers practice building RESTful endpoints, applying validation and error handling, and interacting with a MongoDB database using Mongoose. It serves as a fun and educational way to reinforce backend development skills in a structured, real-world scenario."

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages