DEV Community

Cover image for 20 Things I Learned While Writing a Multiplayer Game with Django and React
Abdelatif IGOUNAD
Abdelatif IGOUNAD

Posted on

20 Things I Learned While Writing a Multiplayer Game with Django and React

Developing a multiplayer game is no small feat. It’s a mix of problem-solving, learning new tools, and navigating through unexpected challenges. Recently, I built an online multiplayer Pong game with Django as the backend and React as the frontend. You can play the game at ft-pong.me, and if you encounter any issues or have suggestions, please report them on our GitHub repository. Here are the key lessons I learned along the way:

1. State Management is Critical for Multiplayer Games

Managing the game state across players and syncing it with the server is a challenging yet essential part of building a multiplayer game. I learned the importance of storing the game state efficiently. Using refs to store state updates helped ensure smooth rendering without triggering unnecessary re-renders.

2. Collision Detection is an Art

Implementing accurate collision detection between the ball and paddles required calculating bounce angles based on the point of impact. Small tweaks to these calculations significantly impacted gameplay, teaching me the importance of precision in game physics.

3. Frontend-Backend Communication

I learned the importance of designing a clear communication protocol between the React frontend and the Django backend. When I switched to production and used Nginx with HTTP/2, I encountered data race issues during connect and disconnect events because requests became faster. Additionally, I discovered that Django Channels' groups are solely a broadcasting system and cannot enumerate members, which required rethinking how to manage connected users effectively.

4. Balancing Game Logic Between Frontend and Backend

Deciding which parts of the game logic should run on the backend versus the frontend was crucial. While the backend was the source of truth for the game state, the frontend handled rendering and minor state updates to improve responsiveness.

5. Leveraging TypeScript for Better Developer Experience

Using TypeScript in the React frontend helped catch errors early and made my codebase easier to maintain. With a project this complex, type safety saved me countless hours of debugging.

6. Testing Multiplayer Features

Testing a multiplayer game was a new challenge for me. I often had to simulate multiple players locally, which required creative solutions and a lot of patience. Debugging real-time interactions added an extra layer of complexity.

7. How to Remove a File from Previous Commits

I initially pushed the .env file for convenience, but when we decided to make the repository public, I learned how to remove it from the entire Git history using commands like git filter-repo. This was a key lesson in securing sensitive data.

8. Choosing the Right Tools for CSS

Initially, I used CSS modules and regular CSS to style the application. While this worked, I realized that switching to Tailwind CSS could significantly speed up the process. Tailwind’s utility-first approach makes it easy to prototype and iterate quickly. Additionally, using a library like ShadCN for pre-built components can save time and effort, enabling you to focus more on functionality rather than designing components from scratch.

9. API Documentation and Testing Tools

Using tools like Swagger to visualize API endpoints made it easier to understand and document the backend. Additionally, Postman was invaluable for testing API requests and debugging issues quickly. These tools streamlined the development process and reduced communication overhead among team members.

10. Handling WebSocket Connections

Managing multiple WebSocket connections for a single player was a challenge. When a player connected from another tab or browser, the old WebSocket was closed, and the player was redirected to the homepage. This ensured a single active connection per player and avoided potential conflicts.

11. Linting Inside Containers

When running the app inside containers, I noticed linting tools were not readily available. To address this, I used flake8 for Python linting. This setup ensured consistent code quality across the team and helped catch issues early in development.

12. Testing and Continuous Development

Testing is essential. Adopting a test-driven development approach helped me catch bugs early and maintain a stable codebase. Additionally, using GitHub Actions for automated testing and linting ensured consistent code quality throughout the project.

13. Adapting to Production Environments

Things run differently in production compared to development. Setting up the deployment process early for continuous development is crucial. For example, running Daphne in production required adding django.setup() to ensure proper initialization.

14. Importance of Debugging Techniques

Learning proper debugging techniques saved me a lot of time. Instead of cluttering my code with print statements, I used better tools and approaches to identify and fix issues more efficiently.

15. Writing Before Coding

Writing down ideas and planning before jumping into coding helped me structure my thoughts and avoid unnecessary rewrites.

16. Taking Breaks When Stuck

When I couldn’t fix a bug, stepping away for a coffee break or doing something else often gave me a fresh perspective and helped me find solutions faster.

17. Understanding Web Security Basics

Having a basic understanding of web security concepts like CORS headers, CSRF saved me a lot of time when working with a project that had separate frontend and backend domains.

18. Async Context and sync_to_async

When working in an asynchronous context, running synchronous functions without wrapping them in sync_to_async can block the event loop, causing hard-to-find bugs. Properly wrapping such functions ensures smooth operation and avoids performance issues.

19. Balancing Learning and Finishing

There was a constant battle between learning new concepts and meeting the project deadline. While learning was rewarding, focusing on the deliverables helped us finish the project sooner.

20. The Joy of Problem-Solving

Finally, building a multiplayer game reminded me why I love coding. Every bug, lag issue, or design challenge was an opportunity to learn and grow as a developer.


This journey was packed with lessons that I hope will inspire and help others in their development journey.

Top comments (0)