<?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>DEV Community: Essential Randomness</title>
    <description>The latest articles on DEV Community by Essential Randomness (@essentialrandom).</description>
    <link>https://dev.to/essentialrandom</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%2Fuser%2Fprofile_image%2F132157%2Fcabb935d-ae68-42ce-9284-d3bb98b10057.png</url>
      <title>DEV Community: Essential Randomness</title>
      <link>https://dev.to/essentialrandom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/essentialrandom"/>
    <language>en</language>
    <item>
      <title>A gentle introduction to REST APIs</title>
      <dc:creator>Essential Randomness</dc:creator>
      <pubDate>Wed, 06 Oct 2021 18:41:48 +0000</pubDate>
      <link>https://dev.to/essentialrandom/a-gentle-introduction-to-rest-apis-3dhg</link>
      <guid>https://dev.to/essentialrandom/a-gentle-introduction-to-rest-apis-3dhg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally written as part of &lt;a href="https://bobadocs.netlify.app/docs/engineering/boba-server/APIs/intro"&gt;the BobaBoard documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;APIs allow applications (clients) to interact with another application (server) without unfettered access to the underlying data, or to the whole set of operations that can be executed on it. In short, an API is a contract defined by the server that establishes a common language that clients can use to communicate with it. Most operations allowed by APIs boil down to fetching, storing or updating data.&lt;/p&gt;

&lt;p&gt;While there are no enforced rules about how APIs should be defined, BobaServer's API implements &lt;a href="https://www.redhat.com/en/topics/api/what-is-a-rest-api"&gt;a special set of API guidelines&lt;/a&gt; called REST. Among the advantages of adhering to REST principles, is that REST APIs are easier to reason about, define, and scale. You can read about the REST principles followed by BobaServer in the &lt;a href="https://bobadocs.netlify.app/docs/engineering/boba-server/APIs/API-guidelines"&gt;API guidelines&lt;/a&gt; page.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a REST API works
&lt;/h2&gt;

&lt;p&gt;REST APIs rely on the HTTP protocol, the same browsers use to navigate most webpages. In particular, a REST API defines a set of &lt;code&gt;endpoints&lt;/code&gt; (URLs) that the client can access to execute operations. A REST API also defines the semantic of the data the client will send to describe the operation parameters, as well as the one of the data it will receive in response.&lt;/p&gt;

&lt;p&gt;For requests, REST APIs rely on &lt;a href="https://www.restapitutorial.com/lessons/httpmethods.html"&gt;&lt;code&gt;HTTP methods&lt;/code&gt;&lt;/a&gt; to execute different operations on the same resource using a single &lt;code&gt;endpoint&lt;/code&gt; associated with it. For responses, REST APIs rely on &lt;a href="https://httpstatuses.com/"&gt;&lt;code&gt;HTTP Status Codes&lt;/code&gt;&lt;/a&gt; to communicate the result of the operation.&lt;/p&gt;

&lt;p&gt;Additional data required as part of the request or response is referred to as the request/response &lt;code&gt;payload&lt;/code&gt;. BobaBoard's API returns responses in the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON"&gt;&lt;code&gt;JSON format&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST API example
&lt;/h2&gt;

&lt;p&gt;As an example, let's define the REST API endpoint that associated with contributions on a thread. This endpoint will live at the &lt;code&gt;/threads/:thread_id/contributions/:contribution_id&lt;/code&gt; address.&lt;/p&gt;

&lt;p&gt;Here are some operations that might be available to the client:&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch the data for a contribution in a thread
&lt;/h3&gt;

&lt;p&gt;The client wants to &lt;strong&gt;fetch&lt;/strong&gt; the contribution with id &lt;code&gt;contribution_123&lt;/code&gt; in the thread with id &lt;code&gt;thread_456&lt;/code&gt;. To achieve this, the client sends a &lt;code&gt;GET&lt;/code&gt; request to the &lt;code&gt;/threads/thread_456/contributions/contribution_123&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Possible responses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;200&lt;/code&gt; with a &lt;code&gt;payload&lt;/code&gt; that contains the contribution data.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;404&lt;/code&gt; if the contribution does not exist.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;401&lt;/code&gt; if the access requires authentication data and none was found in the request.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;GET&lt;/code&gt; requests are the default type of request the browser sends when you access a web page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Create a new contribution in a thread
&lt;/h3&gt;

&lt;p&gt;The client wants to &lt;strong&gt;create&lt;/strong&gt; a contribution with id &lt;code&gt;contribution_123&lt;/code&gt; in the thread with id &lt;code&gt;thread_456&lt;/code&gt;. To achieve this, the client sends a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;/threads/thread_456/contributions/contribution_123&lt;/code&gt; endpoint. The &lt;code&gt;POST&lt;/code&gt; request would also include the contribution data as a &lt;code&gt;payload&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In practice, the contribution id would likely be determined by the server when creating the contribution. In this case, the &lt;code&gt;POST&lt;/code&gt; request would be sent to the &lt;code&gt;/threads/thread_456/contributions/&lt;/code&gt; endpoint instead, and the assigned contribution id would be returned as part of the response &lt;code&gt;payload&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Possible responses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;201&lt;/code&gt; if the contribution was successfully created. This might include a &lt;code&gt;payload&lt;/code&gt; that contains the finalized contribution data.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;401&lt;/code&gt; if creating the contribution requires authentication data and none was found in the request.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;409&lt;/code&gt; if a contribution with the given id already exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;POST&lt;/code&gt; requests are most often used by browsers when submitting forms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Update a contribution in a thread
&lt;/h3&gt;

&lt;p&gt;The client wants to &lt;strong&gt;update&lt;/strong&gt; a contribution with id &lt;code&gt;contribution_123&lt;/code&gt; in the thread with id &lt;code&gt;thread_456&lt;/code&gt;. To achieve this, the client sends a &lt;code&gt;PUT&lt;/code&gt; request to the &lt;code&gt;/threads/thread_456/contributions/contribution_123&lt;/code&gt; endpoint. The &lt;code&gt;PUT&lt;/code&gt; request would also include the updated contribution data as a &lt;code&gt;payload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Possible responses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;201&lt;/code&gt; if the contribution was successfully created. This might include a &lt;code&gt;payload&lt;/code&gt; that contains the finalized contribution data.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;403&lt;/code&gt; if the user is not authorized to update the contribution.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;404&lt;/code&gt; if the contribution does not exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delete a contribution in a thread
&lt;/h3&gt;

&lt;p&gt;The client wants to &lt;strong&gt;delete&lt;/strong&gt; a contribution with id &lt;code&gt;contribution_123&lt;/code&gt; in the thread with id &lt;code&gt;thread_456&lt;/code&gt;. To achieve this, the client sends a &lt;code&gt;DELETE&lt;/code&gt; request to the &lt;code&gt;/threads/thread_456/contributions/contribution_123&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Possible responses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;204&lt;/code&gt; if the contribution was successfully deleted.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;403&lt;/code&gt; if the user is not authorized to delete the contribution.&lt;/li&gt;
&lt;li&gt;An HTTP Status Code of &lt;code&gt;404&lt;/code&gt; if the contribution does not exist.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article was useful, consider &lt;a href="https://twitter.com/EssentialRandom/status/1445568491105316871?s=20"&gt;retweeting it&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>rest</category>
      <category>api</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Documenting Express REST APIs with OpenAPI and JSDoc</title>
      <dc:creator>Essential Randomness</dc:creator>
      <pubDate>Sun, 18 Jul 2021 21:25:06 +0000</pubDate>
      <link>https://dev.to/essentialrandom/documenting-express-rest-apis-with-openapi-and-jsdoc-m68</link>
      <guid>https://dev.to/essentialrandom/documenting-express-rest-apis-with-openapi-and-jsdoc-m68</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;As usual, this article isn't meant as an in-depth guide, but as a documentation of the what, why, and how of certain architectural choices. If you're trying to achieve the same thing and need help, leave a comment!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Updates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;7/20/21:&lt;/strong&gt; Added "documenting models" section. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goals &amp;amp; Constraints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;To document &lt;a href="https://www.bobaboard.com" rel="noopener noreferrer"&gt;BobaBoard&lt;/a&gt;'s REST API.&lt;/li&gt;
&lt;li&gt;Standardize (and document) both the parameters and the responses of various endpoints.&lt;/li&gt;
&lt;li&gt;The documentation should be as close as possible to the source code it describes.&lt;/li&gt;
&lt;li&gt;The documentation should be served through a &lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;docusaurus&lt;/a&gt; instance hosted on a different server.&lt;/li&gt;
&lt;li&gt;(Not implemented): Ensuring endpoints conform to the documented API. While we could use &lt;a href="https://github.com/cdimascio/express-openapi-validator/" rel="noopener noreferrer"&gt;express-openapi-validator&lt;/a&gt;, it doesn't currently support OpenAPI 3.1 (&lt;a href="https://github.com/cdimascio/express-openapi-validator/issues/573" rel="noopener noreferrer"&gt;issue&lt;/a&gt;)

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Consideration:&lt;/em&gt; at least at first, we'd like to report the discrepancies without failing the requests. I'm unsure whether this is supported by this library.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Architecture Flow
&lt;/h3&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%2F3f5exhtdj0qzi71nkq27.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%2F3f5exhtdj0qzi71nkq27.png" alt="How everything interacts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Documentation Page
&lt;/h3&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%2Fd1c30ttwqldflprww9zt.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%2Fd1c30ttwqldflprww9zt.png" alt="The BobaBoard Rest API Documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How To
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Packages used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/swagger-jsdoc" rel="noopener noreferrer"&gt;SwaggerJSDoc&lt;/a&gt;: to turn JSDocs into the final OpenAPI spec (served at &lt;code&gt;/open-api.json&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/redocusaurus" rel="noopener noreferrer"&gt;Redocusaurus&lt;/a&gt;: to embed &lt;a href="https://github.com/Redocly/redoc" rel="noopener noreferrer"&gt;Redoc&lt;/a&gt; into Docusaurus. There are other options for documentation, like any OpenAPI/Swagger compatible tool (e.g. &lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;SwaggerUI&lt;/a&gt;), but Redoc is the nicest feeling one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration (Express)
&lt;/h3&gt;

&lt;h4&gt;
  
  
  OpenAPI Options
&lt;/h4&gt;

&lt;p&gt;These options define the global configuration and settings of your OpenAPI spec. You can find the OpenAPI-specific settings (i.e. the one NOT specific to Redoc) on the &lt;a href="https://swagger.io/specification/#oasObject" rel="noopener noreferrer"&gt;OpenAPI website&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3.1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BobaBoard's API documentation.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Note: indenting the description will cause the markdown not to format correctly.&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
# Intro
Welcome to the BobaBoard's backend API. This is still a WIP.

# Example Section
This is just to test that sections work. It will be written better later.
        `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ms. Boba&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.bobaboard.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ms.boba@bobaboard.com&lt;/span&gt;&lt;span class="dl"&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="na"&gt;servers&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:4200/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Development server&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// These are used to group endpoints in the sidebar&lt;/span&gt;
    &lt;span class="na"&gt;tags&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All APIs related to the /posts/ endpoints.&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/boards/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All APIs related to the /boards/ endpoints.&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;APIs whose documentation still needs work.&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// Special Redoc section to control how tags display in the sidebar.&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-tagGroups&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/posts/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/boards/&lt;/span&gt;&lt;span class="dl"&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="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Which paths to parse the API specs from.&lt;/span&gt;
  &lt;span class="na"&gt;apis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./types/open-api/*.yaml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./server/*/routes.ts&lt;/span&gt;&lt;span class="dl"&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;h4&gt;
  
  
  Documenting Models
&lt;/h4&gt;

&lt;p&gt;OpenAPI specs can contain &lt;a href="https://swagger.io/docs/specification/components/" rel="noopener noreferrer"&gt;a Components section&lt;/a&gt; to define reusable models. These are not automatically documented at this stage (&lt;a href="https://github.com/Redocly/redoc/issues/1528" rel="noopener noreferrer"&gt;workaround issue&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;To add models documentation, add the following section to your top-level configuration.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;models&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-displayName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Models&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// Note: markdown must not contain spaces after new line. &lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
## Contribution
&amp;lt;SchemaDefinition schemaRef="#/components/schemas/Contribution" /&amp;gt;
## Tags
&amp;lt;SchemaDefinition schemaRef="#/components/schemas/Tags" /&amp;gt;
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-tagGroups&lt;/span&gt;&lt;span class="dl"&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;models&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;models&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Add the OpenAPI endpoint
&lt;/h3&gt;

&lt;p&gt;Configure the Express server to surface your spec through an &lt;code&gt;/open-api.json&lt;/code&gt; endpoint. Redocusaurus will use it to retrieve the data to display.&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;import&lt;/span&gt; &lt;span class="nx"&gt;swaggerJsdoc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;swagger-jsdoc&lt;/span&gt;&lt;span class="dl"&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;specs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;swaggerJsdoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/open-api.json&lt;/span&gt;&lt;span class="dl"&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;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;specs&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;h3&gt;
  
  
  Component Specs
&lt;/h3&gt;

&lt;p&gt;Reusable &lt;a href="https://swagger.io/docs/specification/components/" rel="noopener noreferrer"&gt;types&lt;/a&gt; used throughout the documentation.&lt;br&gt;
&lt;code&gt;/types/open-api/contribution.yaml&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Note the /components/schemas/[component name] hierarchy.&lt;/span&gt;
&lt;span class="c1"&gt;# This is used to refer to these types in the endpoint&lt;/span&gt;
&lt;span class="c1"&gt;# documentation.&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Contribution&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;object&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;post_id&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;string&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;
        &lt;span class="na"&gt;parent_thread_id&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;string&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;
        &lt;span class="na"&gt;parent_post_id&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;string&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;uuid&lt;/span&gt;
        &lt;span class="na"&gt;secret_identity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Identity"&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;post_id&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;parent_thread_id&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;secret_identity&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Endpoint Documentation
&lt;/h3&gt;

&lt;p&gt;This should be repeated for every API endpoint you wish to document.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="cm"&gt;/**
 * @openapi
 * posts/{postId}/contribute:
 *   post:
 *     summary: Replies to a contribution
 *     description: Posts a contribution replying to the one with id {postId}.
 *     tags:
 *       - /posts/
 *       - todo
 *     parameters:
 *       - name: postId
 *         in: path
 *         description: The uuid of the contribution to reply to.
 *         required: true
 *         schema:
 *           type: string
 *           format: uuid
 *     responses:
 *       403:
 *         description: User is not authorized to perform the action.
 *       200:
 *         description: The contribution was successfully created.
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 contribution:
 *                   $ref: "#/components/schemas/Contribution"
 *                   description: Finalized details of the contributions just posted.
 */&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/:postId/contribute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoggedIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="c1"&gt;// The endpoint code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Configuration (Docusaurus)
&lt;/h2&gt;

&lt;p&gt;You must update your docusaurus configuration after installing &lt;a href="https://www.npmjs.com/package/redocusaurus" rel="noopener noreferrer"&gt;Redocusaurus&lt;/a&gt;:&lt;br&gt;
&lt;code&gt;docusaurus.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// other config stuff&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// other presets,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redocusaurus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;specs&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="na"&gt;routePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;docs/engineering/rest-api/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// process.env.API_SPEC is used to serve from localhost during development&lt;/span&gt;
            &lt;span class="na"&gt;specUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_SPEC&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[prod_server_url]/open-api.json&lt;/span&gt;&lt;span class="dl"&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="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// See options at https://github.com/Redocly/redoc#redoc-options-object&lt;/span&gt;
          &lt;span class="na"&gt;redocOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;expandSingleSchemaField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;expandResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;pathInMiddlePanel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;requiredPropsFirst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;hideHostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="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;

</description>
      <category>express</category>
      <category>documentation</category>
      <category>javascript</category>
      <category>docusaurus</category>
    </item>
    <item>
      <title>Sending Subscribers Surveys with Ghost, PipeDream and AirTable</title>
      <dc:creator>Essential Randomness</dc:creator>
      <pubDate>Tue, 29 Jun 2021 06:10:12 +0000</pubDate>
      <link>https://dev.to/essentialrandom/sending-subscribers-surveys-with-ghost-pipedream-and-airtable-569i</link>
      <guid>https://dev.to/essentialrandom/sending-subscribers-surveys-with-ghost-pipedream-and-airtable-569i</guid>
      <description>&lt;h5&gt;
  
  
  Note:
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not an "how to" guide, but me explaining my own pipeline to myself as a coherence check, and so I can remember what I have running in the future. If you want to deploy a similar project and have questions, you know where the comment section is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I've recently created a &lt;a href="https://essentialrandomness.com"&gt;newsletter&lt;/a&gt; with a peculiar gimmick: every month(?) paid users (members) receive a survey to decide the topic of the following month. I want to automate this process as much as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  To solve:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Members should receive a new survey each month&lt;/li&gt;
&lt;li&gt;When a user becomes a member mid-month, I want to send them any active survey.&lt;/li&gt;
&lt;li&gt;Keep track of which members have answered which surveys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Pipeline
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j4A-1X9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6pe5lm69jd0y9mzo15w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j4A-1X9h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6pe5lm69jd0y9mzo15w.png" alt="The Pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's an overview of the pipeline. Unfortunately, Excalidraw's sharing features aren't working right now, so I can't link to a larger version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technologies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ghost.org"&gt;Ghost&lt;/a&gt;:&lt;/strong&gt; My CMS of choice, with integrated membership support. I self-host it on my own server, and it connects directly to my Stripe account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pipedream.com/"&gt;PipeDream&lt;/a&gt;:&lt;/strong&gt; Makes it easy to create automations spanning a series of different APIs. Similar to Zapier, but you can run custom code if needed. Generous free tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://surveysparrow.com/"&gt;SurveySparrow&lt;/a&gt;:&lt;/strong&gt; A survey software. This is the first time I'm using it, moving away from Google Forms. Most important: supports webhooks and variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://mailersend.com"&gt;MailerSend&lt;/a&gt;:&lt;/strong&gt; Send email through API. I was already using this for &lt;a href="https://robinboob.herokuapp.com"&gt;RobinBoob&lt;/a&gt;, so adding a new template for the monthly survey was easy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://airtable.com/"&gt;AirTable&lt;/a&gt;:&lt;/strong&gt; To save the data for future reference. I started with Google Sheets, but soon realized I needed a less-brittle solution to feel comfortable with this pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting Member Data into AirTable
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;When a new user signs up as a member, their data is stored in Ghost's own internal DB&lt;/strong&gt;. &lt;em&gt;Technically&lt;/em&gt;, a webhook event is emitted at this stage, but I couldn't get it to work for my purpose (mostly because I couldn't figure out whether they were a paying member upon subscription).&lt;br&gt;
2) &lt;strong&gt;Every two hours, a scheduled PipeDream job uses the Ghost API to fetch members and store them in AirTable.&lt;/strong&gt; This job is independent from the rest of the pipeline, so I can use this table for all future membership-based actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending Surveys
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;Every two hours, a scheduled PipeDream job fetches all surveys with status &lt;code&gt;in progress&lt;/code&gt;, and all the members who are eligible to receive them.&lt;/strong&gt; Then, it creates a list of all the non-surveyed members for each survey.&lt;br&gt;
2) &lt;strong&gt;For each user, PipeDream uses the MailerSend API to send an email with the survey template.&lt;/strong&gt; Surveys have a &lt;code&gt;subscriber_email&lt;/code&gt; parameters, which is used to pre-fill (and skip) the email step of the survey.&lt;br&gt;
3) &lt;strong&gt;For all successful emails sent, the member is then added in the &lt;code&gt;sent_to&lt;/code&gt; column of the Surveys table.&lt;/strong&gt; This prevents repeated emails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking Answers
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;SurveySparrow triggers a PipeDream webhook when a survey gets a new answer.&lt;/strong&gt;&lt;br&gt;
2) &lt;strong&gt;The webhook triggers the "Register Survey Answer" pipeline, which uses the &lt;code&gt;subscriber_email&lt;/code&gt; param to retrieve the member that filled the survey.&lt;/strong&gt; If the param is not present, the survey will display a question asking for the email explicitly, and that will be used instead. &lt;br&gt;
3) &lt;strong&gt;The Member with the corresponding email is added to the &lt;code&gt;answered_by&lt;/code&gt; column in the Surveys table.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential Improvements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Trigger the Update Members job some time after each webhook event is triggered. &lt;/li&gt;
&lt;li&gt;Trigger the Send Survey job whenever an update is made in AirTable rather than (or in addition to) via cron.&lt;/li&gt;
&lt;li&gt;While AirTable also has built-in automations, its runs-per-month are too few for my use on the free plan (100). The pipeline could be simplified if I ever switch to the Plus plan, which features 5000 per month.&lt;/li&gt;
&lt;li&gt;It's possible for someone to use the &lt;code&gt;subscriber_email&lt;/code&gt; param to fake submitting the survey as someone else. This isn't a huge deal for my use case, but a workaround would be to create a unique identifier per survey sent that can then be traced back to the original users.&lt;/li&gt;
&lt;li&gt;Rather than make a new survey manually each month, the survey could be automatically generated through the SurveySparrow API.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pipedream</category>
      <category>nocode</category>
      <category>automation</category>
      <category>ghost</category>
    </item>
    <item>
      <title>Adventures in Hacking Electron Apps</title>
      <dc:creator>Essential Randomness</dc:creator>
      <pubDate>Fri, 01 Mar 2019 04:15:03 +0000</pubDate>
      <link>https://dev.to/essentialrandom/adventures-in-hacking-electron-apps-3bbm</link>
      <guid>https://dev.to/essentialrandom/adventures-in-hacking-electron-apps-3bbm</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer: Hacking apps is often against the Terms of Service. This article is purely theoretical and not an endorsement of the practice. Always Hack Responsibly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Have you ever been &lt;em&gt;extremely annoyed&lt;/em&gt; by a certain aspect of an app or website? &lt;/p&gt;

&lt;p&gt;If you're anything like me, the answer is probably yes. If you're a lot like me, the answer is "yes, often."&lt;/p&gt;

&lt;p&gt;On the web, I can easily solve most grievances thanks to &lt;a href="https://chrome.google.com/webstore/detail/user-javascript-and-css/nbhcbdghjpllgmfilhnhkllmkecfmpld" rel="noopener noreferrer"&gt;extensions that allow you to inject custom CSS and Javascript on any website&lt;/a&gt;. With desktop apps, however, I usually have to live with my pain, hoping the developers will one day see the light and decide to fix the problem (or will finally have the time to prioritize doing so).&lt;/p&gt;

&lt;p&gt;Unless the app is an &lt;a href="https://electronjs.org/" rel="noopener noreferrer"&gt;Electron&lt;/a&gt; app.&lt;/p&gt;

&lt;p&gt;I don't remember how I learned about this possibility—sometimes, there's no stronger driving force than the need to fix bad design.&lt;/p&gt;

&lt;p&gt;In this article, I'm going to talk about how I changed Discord's app code to solve one of my (&lt;a href="https://support.discordapp.com/hc/en-us/community/posts/360037394592-Reduce-horizontal-size-of-window-or-pop-out-chats" rel="noopener noreferrer"&gt;and&lt;/a&gt; &lt;a href="https://support.discordapp.com/hc/en-us/community/posts/360029464911-Make-minimum-window-size-an-optional-feature-" rel="noopener noreferrer"&gt;other's&lt;/a&gt;) biggest grievances: its huge minimum window size.&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%2Fx4rl5d2l4lrozy5vat2y.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%2Fx4rl5d2l4lrozy5vat2y.png" alt="Discord's default minimum window size"&gt;&lt;/a&gt;&lt;/p&gt;
Discord's *huge* minimum window size (940px).



&lt;p&gt;Let's be clear, though: this post—just like any worthy software endeavor—is about the &lt;em&gt;journey&lt;/em&gt; rather than the solution. So follow along my adventure, see how I figured out the needed steps, and learn something about the command line, electron apps and hacking!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: while I use macOS High Sierra, this process can be similarly replicated on other operating systems.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Unraveling Discord's Secrets
&lt;/h1&gt;

&lt;p&gt;Initially, I wanted to learn as much as I could about the Discord application. MacOS has a &lt;a href="https://apple.stackexchange.com/questions/15658/how-can-i-open-a-pkg-file-manually/15665#15665" rel="noopener noreferrer"&gt;UI mechanism to explore application contents&lt;/a&gt;, which is a good "step zero." Manual exploration only goes so far, though, and soon I turned to the command line. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: What does Discord's Process Look Like?
&lt;/h2&gt;

&lt;p&gt;To answer my own question, I ran the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps x | &lt;span class="nb"&gt;grep &lt;/span&gt;Discord
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short, &lt;code&gt;ps&lt;/code&gt; lists all the processes running, &lt;code&gt;x&lt;/code&gt; includes the ones not attached to a shell (e.g. those started by clicking on application icons), and piping this output (&lt;code&gt;|&lt;/code&gt;) to the &lt;code&gt;grep&lt;/code&gt; command displays only the ones featuring the string &lt;code&gt;Discord&lt;/code&gt;. You can learn more at &lt;a href="https://explainshell.com/explain?cmd=ps+x+%7C+grep+Discord" rel="noopener noreferrer"&gt;explainshell.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's the output, edited for readability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1927   ??  S      0:00.08 /Applications/Discord.app/Contents/Frameworks/Electron Framework.framework/Resources/crashpad_handler 
    &lt;span class="nt"&gt;--no-rate-limit&lt;/span&gt; &lt;span class="nt"&gt;--no-upload-gzip&lt;/span&gt; 
    &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/folders/sm/4v5p46v175d3x94qp56r37340000gn/T/Discord Crashes 
    &lt;span class="nt"&gt;--metrics-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/folders/sm/4v5p46v175d3x94qp56r37340000gn/T/Discord Crashes 
    &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://crash.discordapp.com:1127/post 
    &lt;span class="nt"&gt;--handshake-fd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
Process #1927







&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1928   ??  R     34:58.78 /Applications/Discord.app/Contents/Frameworks/Discord Helper.app/Contents/MacOS/Discord Helper 
    &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;renderer &lt;span class="nt"&gt;--no-sandbox&lt;/span&gt; &lt;span class="nt"&gt;--autoplay-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no-user-gesture-required 
    &lt;span class="nt"&gt;--force-color-profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;srgb &lt;span class="nt"&gt;--enable-features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SharedArrayBuffer 
    &lt;span class="nt"&gt;--disable-features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MacV2Sandbox &lt;span class="nt"&gt;--service-pipe-token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5494336596696404231 
    &lt;span class="nt"&gt;--lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en-US 
    &lt;span class="nt"&gt;--app-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Applications/Discord.app/Contents/Resources/app.asar 
    &lt;span class="nt"&gt;--node-integration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--webview-tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="nt"&gt;--no-sandbox&lt;/span&gt; 
    &lt;span class="nt"&gt;--preload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/essential_randomness/Library/Application Support/discord/0.0.254/modules/discord_desktop_core/core.asar/app/mainScreenPreload.js 
    &lt;span class="nt"&gt;--background-color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;#2f3136 --num-raster-threads=2 --enable-zero-copy &lt;/span&gt;
    &lt;span class="nt"&gt;--enable-gpu-memory-buffer-compositor-resources&lt;/span&gt; 
    &lt;span class="nt"&gt;--enable-main-frame-before-activation&lt;/span&gt; 
    &lt;span class="nt"&gt;--service-request-channel-token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5494336596696404231 &lt;span class="nt"&gt;--renderer-client-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
Process #1928





&lt;p&gt;The first process (#1927) seemed to be related to reporting app crashes. I assumed this because of &lt;code&gt;.../crashpad_handler&lt;/code&gt; in the app path, and the &lt;code&gt;url&lt;/code&gt; flag pointing to &lt;code&gt;http://crash.discordapp.com:1127/post&lt;/code&gt; (which is probably the server endpoint crash reports are communicated to).&lt;/p&gt;

&lt;p&gt;The second process (#1928) was more promising. In particular I found the value of the &lt;code&gt;app-path&lt;/code&gt; variable (&lt;code&gt;/Applications/Discord.app/Contents/Resources/app.asar&lt;/code&gt;) worth exploring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Extracting the App Code.
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.asar&lt;/code&gt; extension of the file intrigued me. After a quick google search for "asar files", I found a &lt;a href="https://github.com/electron/asar" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; explaining the format:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Asar is a simple extensive archive format, it works like tar that concatenates all files together without compression, while having random access support.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Luckily, the repository contained information on how to install the asar command- line utility (&lt;code&gt;npm install asar&lt;/code&gt;) and how to extract archived files from asar files. My next step was both obvious and easy. &lt;/p&gt;

&lt;p&gt;Before making any changes, however, I decided to backup the original archive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Backup original file in case of emergency&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /Applications/Discord.app/Contents/Resources/
&lt;span class="nb"&gt;cp &lt;/span&gt;app.asar app_safe_copy.asar

&lt;span class="c"&gt;# Extract app.asar to a folder named "unpacked"&lt;/span&gt;
asar extract app.asar unpacked/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After cracking this metaphorical treasure chest open, it was time to list (&lt;code&gt;ls&lt;/code&gt;) its content!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;unpacked
&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="c"&gt;# output:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; app_bootstrap common        node_modules  package.json

&lt;span class="nb"&gt;ls &lt;/span&gt;app_bootstrap/
&lt;span class="c"&gt;# output:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Constants.js       bootstrap.js       ...       appSettings.js       ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The familiar &lt;code&gt;node_modules/&lt;/code&gt; + &lt;code&gt;package.json&lt;/code&gt; combination pointed to the archive being an npm package, bundling together a bunch of JavaScript files and other things like images. This was clearly important code! Even better, the code was not compiled or encrypted in any way. &lt;/p&gt;

&lt;p&gt;For the first time I thought that (maybe) I could really pull this off! &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: What is This Code?
&lt;/h2&gt;

&lt;p&gt;I explored the JS files by opening them in VsCode to get some insight on the app's structure. This was interesting, but very slow.&lt;/p&gt;

&lt;p&gt;To be faster, I decided to bet on a simple assumption: any file controlling the window width would have had to contain the string "width" itself!&lt;/p&gt;

&lt;p&gt;Also, I could exclude the node_modules folder from my search because npm packages reserve this directory for external libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find all the files containing the string width in the current folder,&lt;/span&gt;
&lt;span class="c"&gt;# but exclude the ones in the node_modules one.&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iRl&lt;/span&gt; &lt;span class="s2"&gt;"width"&lt;/span&gt; ./ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules
&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//app_bootstrap/splash/index.js
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//app_bootstrap/splash/variables.json
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//app_bootstrap/splashScreen.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://explainshell.com/explain?cmd=grep+-iRl+%22width%22+.%2F+%7C+grep+-v+node_modules" rel="noopener noreferrer"&gt;In depth explanation of the command.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This output was disappointing: the files were clearly related to the splash screen, which wasn't what I was looking to change. I tried going up in the top-level Discord folder (&lt;code&gt;/Applications/Discord.app/&lt;/code&gt;) and ran the command again, but the output wasn't much different.&lt;/p&gt;

&lt;p&gt;It seemed my luck had ran out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Sometimes Stepping Back is a Step Forward
&lt;/h2&gt;

&lt;p&gt;Rather than despair, I decided to go back to process #1928. The &lt;code&gt;preload&lt;/code&gt; flag held another interesting path, in a completely different location than the previous one: &lt;code&gt;/Users/essential_randomness/Library/Application Support/discord/0.0.254/modules/discord_desktop_core/core.asar/app/mainScreenPreload.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was time for another adventure!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Once again, I searched for files containing the string "width".&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /Users/essential_randomness/Library/Application&lt;span class="se"&gt;\ &lt;/span&gt;Support/discord/
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iRl&lt;/span&gt; &lt;span class="s2"&gt;"width"&lt;/span&gt; ./ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules
&lt;span class="c"&gt;# Output&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//Preferences
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//settings.json
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//0.0.254/modules/discord_desktop_core/core.asar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
This was when I found out you can grep the content of asar files without unpacking them first. Forgetting steps can lead to interesting discoveries.





&lt;p&gt;This search yielded quite a number of files, so I decided to try to narrow it down further. Since I was looking to change the minimum width, I figured any related variable name would be called either minWidth or min_width. After all, we all take code readability seriously, don't we?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iRl&lt;/span&gt; &lt;span class="s2"&gt;"min_width"&lt;/span&gt; ./ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules
&lt;span class="c"&gt;# Output&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//0.0.254/modules/discord_desktop_core/core.asar

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iRl&lt;/span&gt; &lt;span class="s2"&gt;"minWidth"&lt;/span&gt; ./ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules
&lt;span class="c"&gt;# Output&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//0.0.254/modules/discord_desktop_core/core.asar
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//0.0.254/modules/discord_voice/discord_voice.node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;core.asar&lt;/code&gt; looked really promising! Once again I extracted it and searched for the right file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;0.0.254/modules/discord_desktop_core
&lt;span class="nb"&gt;cp &lt;/span&gt;core.asar core_safe_copy.asar
asar extract core.asar core_unpacked
&lt;span class="nb"&gt;cd &lt;/span&gt;core_unpacked

&lt;span class="c"&gt;# Trying min_width first, as the value is likely a constant.&lt;/span&gt;
&lt;span class="c"&gt;# Constants use, by many code conventions, a capitalized style (i.e. "MIN_WIDTH").&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iRl&lt;/span&gt; &lt;span class="s2"&gt;"min_width"&lt;/span&gt; ./ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules
&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .//app/mainScreen.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Could &lt;code&gt;.//app/mainScreen.js&lt;/code&gt; finally be The One? I immediately opened it, searched for "min_width" and...&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_WIDTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MIN_WIDTH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;940&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;MIN_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MIN_HEIGHT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
 ...JACKPOT!





&lt;h2&gt;
  
  
  Step 5: The Moment of Truth
&lt;/h2&gt;

&lt;p&gt;I knew I had to be onto something. With no clue about whether it would work (but tons of faith) I edited the code:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_WIDTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MIN_WIDTH&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;MIN_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MIN_HEIGHT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now all I needed to do was repack the changed asar file. Again, I made sure to create a backup of core.asar (&lt;code&gt;cp core.asar core_safe_copy.asar&lt;/code&gt;) before going further. Bricking Discord completely was a real possibility here!&lt;/p&gt;

&lt;p&gt;With huge trepidation, I ran the final step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove the original app file and swap it with our edited code, repacked.&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;core.asar
asar pack core_unpacked core.asar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point I restarted Discord, hoping the changes would take effect. I placed my cursor at the app border, started dragging and... IT WORKED!&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv0ejjedxsokhcy7c4eru.gif" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fv0ejjedxsokhcy7c4eru.gif" alt="Discord with minimum window size removed"&gt;&lt;/a&gt;&lt;/p&gt;
Discord with minimum window size removed



&lt;h3&gt;
  
  
  Side Quest: "I Also Like to Live Dangerously"
&lt;/h3&gt;

&lt;p&gt;At this point, I had still one last curiosity. What would have happened if I had messed up while modifying the code?&lt;/p&gt;

&lt;p&gt;I re-extracted the asar file, purposely inserted an invalid JavaScript statement, repacked it and tried to run the app. Unsurprisingly, I got an error!&lt;/p&gt;

&lt;p&gt;This hammered down the importance of back ups. Since I had wisely created a copy of core.asar, I simply put the original code back in its place and the error was gone.&lt;/p&gt;

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

&lt;p&gt;As a final warning, modifying code this way is likely against most Terms of Service (cue the usual Great Power =&amp;gt; Great Responsibility speech). &lt;/p&gt;

&lt;p&gt;Another aspect of code hacking to think about is side effects: Discord is not optimized to be displayed at lower sizes and the UI can be wonky. Since Electron apps use Chromium as a fronted, I modified the UI myself through the Developer Tools console (which Discord kindly makes available under "view &amp;gt; developer &amp;gt; developer tools").&lt;/p&gt;

&lt;p&gt;Please remember that a very valid reason why developers don't want their code modified is that it can cause unexpected bugs in the app itself. If you choose to run a custom version of any code, don't go filing bugs unless you can reproduce them in the original app!&lt;/p&gt;

&lt;h1&gt;
  
  
  A Funny Epilogue
&lt;/h1&gt;

&lt;p&gt;After going through all this, I found out there's an &lt;a href="http://www2.powertrip.net/index.php?threads/resizing-discord-window.382/" rel="noopener noreferrer"&gt;easier way to change Discord's window size&lt;/a&gt; that does not require modifying the source code. &lt;/p&gt;

&lt;p&gt;But, you know, where would the fun be in that?&lt;/p&gt;

&lt;p&gt;Happy Hacking!&lt;/p&gt;

</description>
      <category>electron</category>
      <category>hacking</category>
      <category>commandline</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
