The Design Pattern Design patterns are code templates that can be applied to solve common problems. Object-oriented folks tend to talk about design patterns more than the typical programmer, yet everyone uses patterns in some form, whether they know it or not. If you're familiar with , for example, fetching data after a component has rendered can be stripped down to a few necessary lines of code: React React MyLoadingIndicator MyDataRenderingComponent MyComponent = { [data, setData] = React.useState( ) [isLoading, setIsLoading] = React.useState( ) React.useEffect( { fetchData = () => { response = fetch( ) responseData = response.json() setData(responseData) setIsLoading( ) } fetchData() }, [ ]) (isLoading) <MyDataRenderingComponent data={data}/> } import from "react" import from "path/to/MyLoadingIndicator" import from "path/to/MyDataRenderingComponent" const => () const null const true => () // we need to specify a separate, async function which we call in // this hook, since `useEffect` can't be async const async const await "https://urlToFetchFrom.com" const await false // empty array indicates `useEffect` will run once after render if return return < /> MyLoadingIndicator This pattern can be reused anywhere client-side data fetching and rendering is required. The Problem Design patterns are spread all across the web, in books, in lecture notes - you name it. Wouldn't it be great if there was a single resource that housed these patterns, so that they could be pulled off-the-shelf and used immediately in an application? "Off-the-shelf" is a critical element here. Rolling out a user authentication system, for example, is generally challenging and prone to security risks, especially if you've never done something like it before. An established user authentication pattern, however, could be grabbed and used quickly, with fewer risks involved. Not only that, but a good pattern resource would allow the wider community to evaluate such a user authentication pattern, identifying flaws, sub-optimal design, and security risks. So we've arrived at a problem in software development: . Ideally, we could build a library of crowdsourced design patterns, which facilitates rapid pattern creation, access, reading and evaluation. Good, established patterns would exist under one roof. With this in mind, we set out to build our solution, called . often times, optimal implementations are hard to find Patternite The Plan We wanted Patternite to have a few basic features: Users accounts A design pattern editor A design pattern viewer with comment section A feed of patterns, which can be searched, sorted and filtered These features would constitute a minimum viable product. Anyone would be able to view the design patterns on the site, and users who sign up for an account would be able to create, vote on, and comment on patterns. There would be a feed on the home page, showing the most recently created patterns. This feed could be filtered to show the highest-scoring patterns within a time-frame (day, week, month, millennium), or filtered by programming language. Additionally, all patterns in the Patternite database could be accessed with a basic text search. The Implementation We used the following stack: - Schemaless, great cloud service, and Mongoose is a fantastic ORM MongoDB - Tried and tested, not much else to say! Express - We decided to try out GraphQL rather than a REST-based API, just for fun Apollo - We wanted to ensure good SEO, plus a nice developer experience NextJS The most challenging conceptual aspect of this project was designing the correct data model for the job. Ultimately, this boiled down to a few considerations: MongoDB Users would read patterns far more than they would engage with them Users, patterns and comments could be accessed separately, whereas a vote was intrinsically linked to the pattern it was cast on, and the user that cast it You can see a simplified version of the data model we ended up using below. The lines indicate which object the given ID field is referring to. We created three objects: a object, a object, and a object. To utilize the power our noSQL Mongo database, we went for a denormalization strategy to help optimize our database transactions. User Pattern Comment First, since votes would never need to be accessed outside the user or pattern they exist on, we opted to embed them inside the and objects rather than create a separate object and reference it. This way, whenever a or is read from the database, there is no need to perform a separate query to get its votes. The tradeoff here is that writes are more complicated: whenever a user votes on a pattern, both the corresponding user and pattern document need to be updated. This is okay however, since, as mentioned above, writes in this context are far less frequent than reads. User Pattern Vote User Pattern In addition to this , we embedded comments in the object, and referenced comments in the object. Again, since we wanted fast reads for patterns, embedding the comments made sense. We have a separate object which is referenced by the , just in case we ever want to easily grab a 's comments in the future. Similar to embedding votes in patterns, embedding comments comes with its own set of complications when it comes to writing (not least of which is that each comment can also have its own votes - not shown in the above diagram). Nevertheless, this is an acceptable price to pay to ensure fast, uncomplicated reads. Pattern User Comment User User On the server side, things were relatively straightforward. We used for the base server and to handle user authentication. was used to handle all requests to the API. In retrospect, GraphQL saved us a lot of time implementing the API, since we didn't need to write an endpoint for multiple variations on the same data. For example, we wanted the home page to have a pattern feed, which would display a list of pattern cards. Each card would consist of the pattern name, description, language, creation date, and score. This is a subset of the fields a user would see upon navigating to the actual pattern page. In a traditional REST API, we would either have to implement two endpoints - one for the card, and one for the entire pattern - or, we could (over)fetch the entire pattern, just to display the card. With GraphQL, we could simply ask the API for the data we wanted - one global endpoint, no over-fetching data. Express Apollo GraphQL Finally, we used for the frontend server. Effective search engine optimization (SEO) was an important requirement for us, so client-side rendering solutions like (and the design pattern at the beginning of this article!) were not good options. Although web browsers are good at client-side rendering, it seems that Google’s new - quantifiable metrics that contribute to your site's SEO - are improved through the use of server side frameworks like NextJS. For example, NextJS significantly reduces two Core Web Vital metrics: Largest Contentful Paint (web page loading performance), and Cumulative Layout Shift (visual stability). This is because NextJS fetches data and renders HTML on the server before the webpage touches the browser. All the browser has to handle is some minor JavaScript injection. This contrasts with traditional client-side rendered React, where the browser has to build the HTML DOM (causing unstable visual shifting), and wait for data from external API requests (creating code blocking bottlenecks). NextJS Create React App Core Web Vitals One other small-but-awesome feature of NextJS was the intuitive routing solution. Rather than having to rely on an external dependency like , we could simply organize Patternite's pages into directories to naturally mirror the routing we wanted. React Router The Deployment Below you can see a diagram showing the services we used and how they're connected. First, due to the inherent difficulties of setting up database monitoring, replicas and automatic backups, we opted to use the cloud service, which handles these by default. We went with a server-based architecture, running the frontend and backend inside containers within a . Using Docker was painful (to put things lightly) but it allowed us to containerize the frontend and backend services - ensuring they were self contained and portable, should we ever choose to switch to a different cloud service or move to an orchestration tool like . We used as our reverse proxy, and for caching static assets. MongoDB Atlas Docker DigitalOcean droplet Kubernetes NGINX Cloudflare For continuous deployment, we used to build Docker images of the frontend and backend, and push them to whenever the or branches were updated. We used to listen for changes to the images, pull them, and fire up new containers. We built a staging server based off the branch, so we could test new features in a production environment before merging them with , which would update the production server. Finally, the client is linked to (a privacy-oriented analytics platform), giving us basic information on which pages users are visiting on the site. GitHub Actions Dockerhub development master Watchtower development master Plausible The Result And so, we built a platform for crowdsourced design patterns. You can see the React client-side data fetching and rendering pattern from the beginning of the article below (or see it for real, ). here Overall, Patternite was a fun project, and we're continually working to improve it. The next steps are to implement a notification system, and server-side caching with in order to reduce server load. Redis If you have an interesting design pattern that you've used in your own work, we encourage you to contribute it to ! We'd also be grateful for any feedback or questions you might have - checkout the page to get in touch. We also make short, to-the-point YouTube videos on various patterns in Patternite, which you can find . Patternite contact here