DEV Community

Cover image for Implementing React and Django with Docker and Nginx
Oscar
Oscar

Posted on

Implementing React and Django with Docker and Nginx

If you’re ever used Django before, you’ve probably wanted to integrate it with a framework like React. And if you’ve gone down this path, you’ve probably spent a few hours looking for an easy way to do this, and you’ve probably realized that there really isn’t. While there are alternative solutions such as this, I wanted to make my own implementation of React and Django.

Hey you! Before I begin, if you’d like to click off this post and run the project right now, you can! As part of my design philosophy, you can spin up the project with a single command, docker compose up -w. But if you’d like to stick around and see how this all works, be my guest. Speaking of design philosophy...

A quick note on design philosophy 🧠

The goals of this project were simple: make a project that can be run with a single command, ensure that this project still behaves like a normal Django or React project, and maintain the level of security that Django does.

Docker 🐋

For obvious reasons, everything is dockerized and managed by a single docker compose file (I don’t have a production-ready docker compose file yet, as I haven’t deployed a project with this).

The docker part of this project is pretty simple, but if you’re curious I’d invite you to read through the docker compose file yourself. Just for a quick overview, the compose file has 4 containers; one for Nginx, one for PostgreSQL, one for Django, and one for React. The PostgreSQL and React containers spin up first, then the Nginx container, and finally the Django container, which waits to start until PostgreSQL is ready using a health check like so:

healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USER} -d ${DATABASE_NAME}"]
      interval: 1s
      retries: 3 
      start_period: 3s
      timeout: 3s
Enter fullscreen mode Exit fullscreen mode

Also, the only port exposed to our computer (localhost) is 1337. Any other ports that are “exposed” in this project are only exposed to other Docker containers.

And finally, the project supports live reloading by default! This way, the project still behaves like a normal Django or React project, which is in line with the design philosophy.

Nginx 💻

Nginx lets us run the frontend and the backend on the same domain. Django isn’t very happy when it receives requests from a different domain, so this makes our lives a lot easier.

If you’re confused about what Nginx’s place is in this project, here’s a little diagram that might help you:
A diagram of the project

There are a few different routes listed in the Nginx folder, but they’re pretty simple. Going to /api, /admin, or /static will proxy you to the backend, and going anywhere else (/) will proxy you to the frontend! Speaking of which, most of our users will be going straight to the frontend, so why not talk about that next?

React (the frontend) 🖥️

The frontend is just a SPA (Single Page Application) running React, but that doesn’t mean it’s exactly simple. Alongside React is React Redux for global state management and React Router V6 for routing purposes. Every interaction that the frontend makes with the backend is through Javscript’s fetch API (AJAX). For instance, our login function:

async function login({ username, password }) {
  const config = {
    headers: { "Content-Type": "application/json" },
    credentials: "include",
    method: "POST",
    body: JSON.stringify({ username, password }),
  };
  const response = await fetch("/api/accounts/login/", config);
  if (response.ok) {
    store.dispatch(checkAuthenticated(LOGIN_SUCCESS));
  } else {
    store.dispatch(checkAuthenticated(LOGIN_FAIL));
  }

  return store.getState().auth.isAuthenticated;
}
Enter fullscreen mode Exit fullscreen mode

It takes in a username and a password, then sends a POST request to our backend. If response.ok is True, then we’re logged in, and if it isn’t, we aren’t logged in. This in itself is simple enough, but it gets tricky when you consider how to manage this state across multiple pages. This is where React Redux comes in.

While I’m not going to go into full detail about how React Redux works, I’ll give a brief overview of how it helps our frontend manage authentication. If you look at the conditionals on the login function, you’ll see a store.dispatch function. If we logged in successfully, the store.dispatch function tells the store that we are now authenticated, so the next time we want to load a page that requires authentication, we can just “ask” the store if we’re authenticated before proceeding.

But what about when the user reloads the page? Well, we can just send a request to the backend to check if we’re authenticated, and update the store based on that. This is done in the AuthenticatedRoutes.jsx file, and it’s also where I’d like to point out how useful React Router V6 is. I can easily redirect the user to the login page if they aren’t logged in, or if they are, I can also choose not to redirect them. Here's an example from AuthenticatedRoutes.jsx:

const isAuth = store.getState().auth.isAuthenticated;

    if (isAuth) {
      return children;
    }

    return <Navigate to="/login" replace={true} />;
  }
  return <p>Loading</p>;
Enter fullscreen mode Exit fullscreen mode

If you don’t know how Django (or any other backend framework) authenticates users, you might be a bit confused. We haven’t manually sent anything to the backend that shows proof of authentication (except for a username and password), so how are we authenticating the user? Indeed, there are a few different methods of authentication, but the most popular by far is session authentication. I’ll leave it to the reader to learn about session authentication, but just know that Django sends the session token through cookies, so we don’t have to manually send anything.

The CSRF token 🪙

We do have to manually send something from the frontend to the backend though: the CSRF token (again, I’ll let the reader learn about the CSRF token on their own). This is because we’re using the Django Rest Framework (DRF) on top of Django to facilitate communication between the frontend and backend, and DRF requires us to send our CSRF token in the cookies and in the headers. Django sends the CSRF token as a cookie every time we log in, and we have to get the cookie into Javascript so we can send it with every request that needs it. This is quite easy, as we can just use a Javascript function provided by Django themselves:

const getCookie = (name) => {
  let cookieValue = null;
  if (document.cookie && document.cookie !== "") {
    let cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
      let cookie = cookies[i].trim();
      if (cookie.substring(0, name.length + 1) === name + "=") {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
};

export default getCookie;
Enter fullscreen mode Exit fullscreen mode

Now when we want to make POST requests – or any other kind of request that requires a CSRF token – to the backend, we can just get the CSRF token like so:

const config = {
    headers: {
      "X-CSRFToken": getCookie("csrftoken"),
    },
    ...
  };
Enter fullscreen mode Exit fullscreen mode

That’s about it for the frontend!

Django (the backend) 👉

The backend is incredibly simple. 90% of the logic is in one file , and it’s only 50 lines of code. For instance, here's the logout function:

@api_view(["POST"])
def logout_user(request):
    try:
        logout(request)
        return Response({"success": "You have been logged out"}, status=200)
    except:
        return Response({"error": "Something went wrong"}, status=403)
Enter fullscreen mode Exit fullscreen mode

There is one quirk that I would like to mention. Normally, when you send a cookie, Django sets the HTTPOnly attribute of the cookie to True, meaning that the cookie can’t be accessed by Javascript (which can offer a little bit of security). We have to disable this attribute in order to allow the getCookie function from Django’s documentation to actually read the cookie. While this may sound dangerous, Django themselves say that this is perfectly safe.

Wrapping up 🎁

And… that’s about it. I think I’ve stayed aligned with the design philosophy that I mentioned at the start of this. What do you think? Are there any improvements that you would make (and if there are, please feel free to make a pull request!).

You can view the project on Github here!

Top comments (3)

Collapse
 
prakirth profile image
Prakirth Govardhanam

Cool design philosophy. Sounds perfectly like common sense 😊
This was something I was planning to do using MaterialUI and React in my Django project. But need to learn how to use Nginx and Docker. Will dive in and see if I can understand well. Thanks for this post! 👍🏾

Collapse
 
kurealnum profile image
Oscar

Thank you for the thoughtful comment! If there's anything that's poorly described or explained in the codebase, please do let me know.

Collapse
 
prakirth profile image
Prakirth Govardhanam

Sure thing! I’m planning to fiddle with my project tomorrow. Will let you know if I do find any😊