<?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: Roman Sorin</title>
    <description>The latest articles on Forem by Roman Sorin (@romansorin).</description>
    <link>https://forem.com/romansorin</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%2F250524%2Fd4d05bce-465c-4cca-a629-780c813b4456.png</url>
      <title>Forem: Roman Sorin</title>
      <link>https://forem.com/romansorin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/romansorin"/>
    <language>en</language>
    <item>
      <title>Serializing OpenAI API Responses for Logging with Pydantic</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Sat, 03 Feb 2024 03:16:36 +0000</pubDate>
      <link>https://forem.com/romansorin/serializing-openai-api-responses-for-logging-with-pydantic-14df</link>
      <guid>https://forem.com/romansorin/serializing-openai-api-responses-for-logging-with-pydantic-14df</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateEmbeddingResponse&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_OPENAI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CreateEmbeddingResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Some embeddings to generate&lt;/span&gt;&lt;span class="sh"&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;# Using Pydantic's `model_dump_json` method,
# we can easily serialize any OpenAI responses
&lt;/span&gt;&lt;span class="n"&gt;serialized_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Now that our response is serialized,
# we can log and store it as needed
&lt;/span&gt;&lt;span class="n"&gt;openai_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenAiResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;serialized_response&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 model fields
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why you might consider serializing responses
&lt;/h3&gt;

&lt;p&gt;When working with external APIs, I lean heavily on logging as much information as I can for requests and responses. Recently, I've been working on a side project that relies on OpenAI's &lt;a href="https://platform.openai.com/docs/guides/embeddings"&gt;embeddings&lt;/a&gt; and &lt;a href="https://platform.openai.com/docs/guides/text-generation/chat-completions-api"&gt;chat completions&lt;/a&gt; APIs, as well as Amazon Textract's &lt;a href="https://aws.amazon.com/textract/"&gt;OCR service&lt;/a&gt;. For these types of external APIs, logging entire responses can be incredibly useful for many reasons, whether it's trying to iron out pricing for a product or needing to track if you're receiving a non-deterministic output (&lt;a href="https://platform.openai.com/docs/guides/text-generation/reproducible-outputs"&gt;despite configuring for reproducibility&lt;/a&gt;). Over time, the quality of outputs can drift over time, and you'll inevitably need to iterate on input prompts as your product evolves or parameters change, which is where detailed logging provides the opportunity to look at how cost and quality may be changing - almost like a passive A/B test.&lt;/p&gt;

&lt;p&gt;For most small to medium-sized projects, I'll typically store these logs as a dedicated table or explicit metadata columns on an existing table using Postgres. You &lt;em&gt;could&lt;/em&gt; break a response object and its items into dedicated columns, but APIs tend to change over time (sometimes without warning), and I don't want to spend time maintaining something that will be infrequently used for debugging, replaying requests, or analysis.&lt;/p&gt;

&lt;p&gt;So, as part of this side project, I was attempting to store the response directly into an SQLAlchemy model like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CreateEmbeddingResponse&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_OPENAI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CreateEmbeddingResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Some embeddings to generate&lt;/span&gt;&lt;span class="sh"&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;# This will throw - we can't store the object directly!
&lt;/span&gt;&lt;span class="n"&gt;openai_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OpenAiResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAiResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# embeddings_response is a JSONB column
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This, of course, would not work. Trying to insert this directly into the model would produce an error like &lt;code&gt;TypeError: Object of type CreateEmbeddingResponse is not JSON serializable&lt;/code&gt;. While exploring solutions, &lt;a href="https://gist.github.com/CivilEngineerUK/dbd328b72ebee77c3471670bb91fa6df"&gt;I came across a gist&lt;/a&gt; that created an explicit &lt;code&gt;serialize_completion&lt;/code&gt; function to accomplish this. While the approach is straightforward (and unblocked me temporarily while trying to solve an unrelated task!), it’s more work than necessary and would require more maintenance and a similar manual approach for other response types.&lt;/p&gt;

&lt;h3&gt;
  
  
  When in doubt, just read the source code
&lt;/h3&gt;

&lt;p&gt;After creating another serializer for the embeddings response, I was frustrated. What if I miss a key or the API changes silently, meaning I may be losing information or have exceptions creeping up? There are many solutions, but I decided I'd jump into the definition for the &lt;code&gt;CreateEmbeddingResponse&lt;/code&gt; type. Luckily, OpenAI represents all of their responses as Pydantic models. &lt;a href="https://docs.pydantic.dev/latest/"&gt;Pydantic&lt;/a&gt; is an extremely powerful data validation library that lets us solve this serialization issue natively, instead of having to come up with our own solution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using Pydantic's `model_dump_json` method,
# we can easily serialize any OpenAI responses
&lt;/span&gt;&lt;span class="n"&gt;serialized_response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddings_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_dump_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reviewing the documentation for &lt;code&gt;pydantic.BaseModel&lt;/code&gt;, we can find the &lt;a href="https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_dump_json"&gt;&lt;code&gt;model_dump_json()&lt;/code&gt; method&lt;/a&gt; that allows us to serialize it directly to a JSON-encoded string. A simple and obvious solution, but a source of frustration if you don’t know (or forget) where to look. Another personal reminder of why enforcing strong typing is so useful!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;A closing note&lt;/strong&gt;: I’m currently building an ETL product based around documents and document-heavy workflows. If you or your organization might be interested, &lt;a href="https://romansorin.com/contact"&gt;please let me know&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://romansorin.com"&gt;romansorin.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pydantic</category>
      <category>openai</category>
      <category>logging</category>
      <category>python</category>
    </item>
    <item>
      <title>Using Django's JSONField? You probably don't need it. Here's why</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Wed, 06 Apr 2022 00:59:26 +0000</pubDate>
      <link>https://forem.com/romansorin/using-djangos-jsonfield-you-probably-dont-need-it-heres-why-17ck</link>
      <guid>https://forem.com/romansorin/using-djangos-jsonfield-you-probably-dont-need-it-heres-why-17ck</guid>
      <description>&lt;p&gt;In my current role, we use Django with Django Rest Framework (DRF) to power our platform’s API, and leverage PostgreSQL to store both relational and nonrelational data. As part of the platform, we run asynchronous, on-demand batch jobs that can operate up to half a million rows at once.&lt;/p&gt;

&lt;p&gt;Each of these jobs is represented by a row in a table and contains both top-level, single-responsibility columns as well as “deeper” structures that may contain more arbitrary data. As a working proof-of-concept, we avoided over-optimizing early and didn’t have a clear definition of what these job outputs might look like. We added JSON columns to our job model that would store different forms of data - metadata, debug output, and namely, job report output.&lt;/p&gt;

&lt;p&gt;While we didn’t need to expose metadata or debug output to our API, returning the report data to describe the job results was the main requirement. This data varied little in size and complexity, only maintaining a couple of keys and a few levels of nesting.&lt;/p&gt;

&lt;p&gt;This job model did not have any special qualities. It was defined and represented like any other type of model within our Django app, except for additional JSONB columns to represent arbitrary and unnormalized data.&lt;/p&gt;

&lt;p&gt;Unexpectedly, we experienced major slowdowns both in API response time and internal queries made through an RDBMS. At a point, requesting a single one of these jobs would take &lt;em&gt;upwards of 5 minutes&lt;/em&gt;. When JSONB columns were omitted from queries, the query had a subsecond execution time.&lt;/p&gt;

&lt;p&gt;Interestingly, we found that converting all columns that did not need to use any of JSONB’s benefits – such as indexing and advanced querying – significantly improved our application’s performance. While I’m doubtful that this was the true culprit, it did get us to our desired performance in the shortest time possible.&lt;/p&gt;

&lt;p&gt;With that personal anecdote, let’s explore the differences between JSONB and JSON, and how you can use JSON in your Django apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSONB vs JSON, and how they’re handled
&lt;/h2&gt;

&lt;p&gt;Both the JSON and JSONB data types are used for storing JSON data in PostgreSQL tables. These data types are &lt;em&gt;almost&lt;/em&gt; identical, different only in how these values are &lt;a href="https://www.postgresql.org/docs/current/datatype-json.html"&gt;stored in the database&lt;/a&gt;, and the efficiency with which operations [1] are performed.&lt;/p&gt;

&lt;p&gt;Notably, JSON stores the exact input text, whereas JSONB instead stores a decomposed binary format. As a result, there’s a small amount of write/conversion overhead (converting from text to binary), making storing input on this field slightly slower overall. The binary format of JSONB allows for significantly faster processing and operations, as the field does not have to be reparsed each time.&lt;/p&gt;

&lt;p&gt;In addition to the fields represented in a different format, JSONB allows for indexing of keys in the field, which allows for efficient arbitrary key lookups. This would take significantly longer if attempting to search within a JSON field, as you would have to both parse the field and perform a search within a &lt;code&gt;text&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In summary,&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores an exact copy of the input text, including “semantically-insignificant” whitespace, duplicate keys, and ordering.&lt;/li&gt;
&lt;li&gt;The last value of a duplicate key is used.&lt;/li&gt;
&lt;li&gt;Must reparse the field for each operation, causing these operations to take significantly longer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;JSONB:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores a decomposed binary format, which does not preserve whitespace (outside of values), the order of keys, or duplicate keys. Only the last value of duplicate keys is kept.&lt;/li&gt;
&lt;li&gt;No reparsing is needed and operations over the field are significantly quicker&lt;/li&gt;
&lt;li&gt;Can take up &lt;a href="https://heap.io/blog/when-to-avoid-jsonb-in-a-postgresql-schema"&gt;more disk space&lt;/a&gt; than a regular JSON column&lt;/li&gt;
&lt;li&gt;Supports indexing of keys, significantly improving the performance of lookups&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;p&gt;Deciding which format of JSON to use will largely depend on the needs of your application. Generally, the &lt;a href="https://www.postgresql.org/docs/current/datatype-json.html"&gt;Postgres manual suggests&lt;/a&gt; that most applications should use the JSONB field, except in specialized circumstances or legacy applications where keys would not be able to be re-ordered on retrieval.&lt;/p&gt;

&lt;p&gt;In normal usage, this makes sense as you are likely to be storing some form of structured yet arbitrary data that you may need to perform operations or lookups on. However, we found that a JSON field was a better option for our use case, as we stored large amounts of varying data to be only used for retrieval &lt;em&gt;without manipulation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Deciding between JSONB and JSON will depend on several factors, such as requirements for &lt;strong&gt;indexing, lookups, operations, and retrieval.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer is,&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use JSON when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are only storing and retrieving data within this field&lt;/li&gt;
&lt;li&gt;You do not need JSONB’s benefits and would prefer to save write time and disk space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use JSONB when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need indexing of keys within the object&lt;/li&gt;
&lt;li&gt;You will perform lookups over this field&lt;/li&gt;
&lt;li&gt;You will perform operations over this field, such as sorting, ordering, or slicing, before working with the representation in your application [2].&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Switching from JSONB to JSON
&lt;/h2&gt;

&lt;p&gt;If you’re using Postgres with Django and importing the JSONField object, you’re using JSONB. This is the default in recent Django versions and is a sensible choice for most applications, but you may find that you’d prefer a standard JSON based on your data and workflow.&lt;/p&gt;

&lt;p&gt;If you find that you don’t need JSONB’s features, you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can use a &lt;code&gt;text&lt;/code&gt; field, which will store your JSON in the exact input format. There are a few caveats to this approach:

&lt;ol&gt;
&lt;li&gt;You will need to serialize and deserialize the object when working with it, especially in an API context&lt;/li&gt;
&lt;li&gt;A native text field does &lt;em&gt;not&lt;/em&gt; provide JSON input validation. To ensure that you’re storing valid JSON objects, you’ll need to validate this before inserting it into your database.&lt;/li&gt;
&lt;li&gt;You will not be able to retroactively cast the field to a &lt;code&gt;jsonb&lt;/code&gt; if you later decide to use JSONB format.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;
&lt;li&gt;If you want the convenience of a package, you can use packages like &lt;a href="https://pypi.org/project/django-jsonfield/"&gt;django-jsonfield&lt;/a&gt; or &lt;a href="https://pypi.org/project/jsonfield/"&gt;jsonfield&lt;/a&gt;, which will handle validation and serialization by default. If you opt-out of using JSONB, this is the preferred option.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’re using other databases like MySQL, you may opt for a package like &lt;a href="https://pypi.org/project/django-mysql/"&gt;django-mysql&lt;/a&gt;, which provides support for JSON fields and extends the functionality of MySQL in your Django application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using JSON with Django Rest Framework
&lt;/h2&gt;

&lt;p&gt;To solve the performance issue, we opted to switch to using the &lt;code&gt;JSONField&lt;/code&gt; field provided by &lt;a href="https://pypi.org/project/django-jsonfield/"&gt;django-jsonfield&lt;/a&gt;, allowing us to remove JSONB from the model entirely. With our new JSON fields, we suddenly found that the data provided by our API to our processing/job service was causing exceptions when we were attempting to extract and load information from the object. Where we originally were inferring a native JSON object, which was already deserialized by Django, we instead were receiving a JSON object in the form of a string. Our data providers didn’t expect this and quickly began failing when we attempted to use any metadata from our API.&lt;/p&gt;

&lt;p&gt;After some experimentation, we found that responses needed to have these new JSON fields deserialized manually before being returned in the API. Django handled this originally, but switching to a non-default field required some adjustment.&lt;/p&gt;

&lt;p&gt;Assuming that you’re using serializers within Django Rest Framework (DRF), you can update the serializer for the field to be a &lt;a href="https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield"&gt;SerializerMethodField&lt;/a&gt;, which can be used to add any arbitrary data to your object’s serialized representation or change the representation of an existing field. Here’s how you can deserialize the JSONField within a serializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.fields&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SerializerMethodField&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models.cluster&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClusterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SerializerMethodField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Cluster&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forces the field to deserialize from a string to a Python &lt;code&gt;dict&lt;/code&gt;. If you intend on using this field in more than one serializer, you may benefit from adding a property to the model to automatically deserialize this field instead of within your serializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jsonfield&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonfield&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JSONField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nd"&gt;@property&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_metadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Further anecdotes
&lt;/h2&gt;

&lt;p&gt;Admittedly, there were too many confounding factors relating to this issue to blame for the usage of the JSONB field. Because we had resolved the problem with this change, I stopped researching possible causes beyond what I had originally found and stopped looking further into performance benchmarks.&lt;/p&gt;

&lt;p&gt;Jobs in this table and the corresponding column were being populated by data produced through a disconnected processor. The output had been sent through our API at the time (this has since changed as another performance consideration), and inserts were being handled by our main Django application.&lt;/p&gt;

&lt;p&gt;Based on our usage, there may have been issues with indexing, or the query planner was somehow unable to work with the data in this column across all of our jobs. As we experiencing slowdowns both in our application and using an RDBMS, I’m unsure if this was amplified by passing the model to a DRF serializer, which has an overhead of its own; or if our managed database simply could not handle returning this data as part of our queries.&lt;/p&gt;

&lt;p&gt;Interestingly, we had millions of rows in another table that had five times the number of JSONB columns with no obvious differences in performance. These JSONB columns were fully queryable and responded in a reasonable amount of time, concerning the size of the data being returned, despite no difference in the model’s definition of the JSON field. Many of these columns had significantly more data than the related column in any of the problematic jobs, and yet they were handled significantly better by the RDBMS. Our problematic table with only a few rows took up around 16MB of space, whereas switching to a normal JSON reduced table size to a few KB. The table with millions of rows and sets of JSONB columns held 20GB+ of data and responded within seconds to both complex and simple queries.&lt;/p&gt;

&lt;p&gt;While this change likely isn’t the single reason behind performance improvement, it helped recognize that there is a need to make considerations about how you’re storing and handling related data. As our system matured and we had more examples of what distinct outputs of these jobs may look like, we eventually normalized this report column into its separate table, allowing us to improve performance even further by lazily loading report data while keeping our API less complex. After reviewing the output of several distinct jobs, we extracted upwards of 20 attributes into dedicated columns with explicit data types, which will help significantly with performance over time as the size of the data stored in this table continues to grow.&lt;/p&gt;

&lt;p&gt;I don’t have the full answer to what happened here, as I didn’t get to spend more time investigating. If you have any thoughts on what may have occurred beyond what I’ve covered here or have experienced this yourself, I'd love to chat.&lt;/p&gt;




&lt;h3&gt;
  
  
  Footnotes
&lt;/h3&gt;

&lt;p&gt;[1] Operations here refer to things like search, sorting, slicing, and ordering done directly through database queries.&lt;/p&gt;

&lt;p&gt;[2] "Working with the representation in your application" refers to retrieving your object from the database, storing it in memory/as a local variable, and then working with the local object. At this point, it wouldn't matter what format the JSON is stored in, as you'll only get JSONB benefits when operations are done within the database context.&lt;/p&gt;

</description>
      <category>django</category>
      <category>postgres</category>
      <category>python</category>
      <category>database</category>
    </item>
    <item>
      <title>Deleting Git branches in bulk</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Wed, 09 Mar 2022 00:02:01 +0000</pubDate>
      <link>https://forem.com/romansorin/deleting-git-branches-in-bulk-2925</link>
      <guid>https://forem.com/romansorin/deleting-git-branches-in-bulk-2925</guid>
      <description>&lt;p&gt;Deleting several Git branches at once with commands like &lt;code&gt;git branch -D&lt;/code&gt; can become a real headache. At any time, I have between 5-15 branches per project, all of which I may complete at random times based on priority and state. If you forget to delete your local branches after merging your work, you may end up having weeks of stale branches living within your project.&lt;/p&gt;

&lt;p&gt;Thankfully, there’s an easier alternative to this, and we can run a single command using commands like a pipe (&lt;code&gt;|&lt;/code&gt;) and &lt;code&gt;grep&lt;/code&gt; to delete several branches at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The command
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Deleting branches in bulk can be done in a single line:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'FIX|FEAT'&lt;/span&gt; | xargs git branch &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s take a look at what’s being done here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we define our first command, &lt;code&gt;git branch&lt;/code&gt;, on the left side of the first pipe. This lists all of the branches within your project.&lt;/li&gt;
&lt;li&gt;Second, we define a &lt;code&gt;grep&lt;/code&gt; command which will search the output of our branch command. Since we are using a pipe, the output of &lt;code&gt;git branch&lt;/code&gt; is passed to grep. To match several branch patterns at once, we define the &lt;code&gt;-E&lt;/code&gt; flag. This allows us to match several strings while also supporting extended regular expressions. We also may have case variations between “FIX”, and “fix”, meaning our grep should be case-insensitive. We do this with the &lt;code&gt;-i&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;Third, we define the normal branch deletion command on the right side of the last pipe, and also supply &lt;code&gt;xargs&lt;/code&gt; as part of this command. &lt;strong&gt;xargs&lt;/strong&gt;, or extended arguments, will take the left side of the last pipe (our grep command) and pass it as arguments to &lt;code&gt;git branch -D&lt;/code&gt;, calling the branch deletion command for each matched branch.&lt;/li&gt;
&lt;li&gt;As a result, all branches that start with “FEAT” or “FIX” (case insensitive) are automatically deleted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Visualizing the deletion
&lt;/h3&gt;

&lt;p&gt;To visualize this, let’s run each of these commands individually:&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="nv"&gt;$ &lt;/span&gt;git branch

FEAT/RS-123
FIX/RS-456
IMPROV/RS-13
feat/RS-96
fix/RS-56
REF/RS-250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;git branch&lt;/code&gt; returns all of the existing local branches. Now, let’s try to find all of the branches that we want to delete:&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="nv"&gt;$ &lt;/span&gt;git branch | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'FIX|FEAT'&lt;/span&gt;

&lt;span class="k"&gt;*&lt;/span&gt; FEAT/RS-123
&lt;span class="k"&gt;*&lt;/span&gt; FIX/RS-456
&lt;span class="k"&gt;*&lt;/span&gt; feat/RS-96
&lt;span class="k"&gt;*&lt;/span&gt; fix/RS-56
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;grep&lt;/code&gt; command highlighted the matching branches and each matched phrase. Using the pipe operator, we passed the output of &lt;code&gt;git branch&lt;/code&gt; into the &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we typed the deletions manually for each branch, instead of using the full command, it would look something like this:&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="nv"&gt;$ &lt;/span&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; FEAT/RS-123
Deleted branch FEAT/RS-123 &lt;span class="o"&gt;(&lt;/span&gt;was 1b39b22&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; FIX/RS-456
Deleted branch FIX/RS-456 &lt;span class="o"&gt;(&lt;/span&gt;was ea9973f&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; feat/RS-96
Deleted branch feat/RS-96 &lt;span class="o"&gt;(&lt;/span&gt;was 6c114fb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;git branch &lt;span class="nt"&gt;-D&lt;/span&gt; fix/RS-56
Deleted branch fix/RS-56 &lt;span class="o"&gt;(&lt;/span&gt;was 5a82faa&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now see that instead of a single command that takes advantage of pipes, we had to type 6 different commands, each of which has a standard output of its own – making it tougher to keep track of what was deleted, and what still needs to be cleaned up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing data using pipes
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.man7.org/linux/man-pages/man2/pipe.2.html"&gt;Many resources exist&lt;/a&gt; on pipes and their usages, but it may help to visualize usage in the context of deleting this branch deletion command. In our case, the condensed data flow looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch -&amp;gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'FIX|FEAT'&lt;/span&gt; -&amp;gt; xargs git branch &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each side of our pipe has a “standard input” (STDIN) and “standard output” (STDOUT). The STDOUT is the result of running a command and is passed in as STDIN to the next command. In our flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch &lt;span class="o"&gt;(&lt;/span&gt;STDOUT&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt;
&lt;span class="o"&gt;(&lt;/span&gt;STDIN&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'FIX|FEAT'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;STDOUT&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt;
&lt;span class="o"&gt;(&lt;/span&gt;STDIN&lt;span class="o"&gt;)&lt;/span&gt; xargs git branch &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;STDOUT&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is an overly simplistic explanation of what is happening behind the scenes, it hopefully provides an idea of how commands and their outputs can be spliced together using pipes.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>git</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Disabling the Tailwind input ring</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Tue, 01 Feb 2022 13:30:57 +0000</pubDate>
      <link>https://forem.com/romansorin/disabling-the-tailwind-input-ring-4624</link>
      <guid>https://forem.com/romansorin/disabling-the-tailwind-input-ring-4624</guid>
      <description>&lt;p&gt;If you’ve ever worked with &lt;a href="https://tailwindcss.com"&gt;Tailwind&lt;/a&gt; components or &lt;a href="https://tailwindui.com"&gt;TailwindUI&lt;/a&gt;, you’ve probably come across the “focus ring”. On inputs (checkboxes, fields, etc.), this ring is shown when you’ve clicked or tabbed onto a component, indicating that you have selected it. Here’s an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzb2uzz25u9jv9qg202zl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzb2uzz25u9jv9qg202zl.png" alt="Enabled ring on checkbox" width="28" height="28"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling the ring
&lt;/h2&gt;

&lt;p&gt;I’ve seen myself and others try to disable this by simply calling the &lt;code&gt;focus:ring-0&lt;/code&gt; utility. This sort of works, but gives us this weird shadowing effect on the corners of our input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjeaawinckw0gjgioary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjeaawinckw0gjgioary.png" alt="Disabled ring, but persistent offset" width="24" height="24"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, Tailwind will include this ring and an offset on certain input elements like checkboxes. If you’d rather not have this ring on all interactions, &lt;strong&gt;you can easily disable it:&lt;/strong&gt;&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;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"focus:ring-0 focus:ring-offset-0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, we’re able to get a nice looking checkbox, without the “ugly” looking ring:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmlq0jtzyhdrv5znsfzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmlq0jtzyhdrv5znsfzk.png" alt="Fully disabled ring on a checked checkbox" width="32" height="29"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping things clean
&lt;/h3&gt;

&lt;p&gt;To reduce redundancy across my project, I created a class that I can globally apply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.without-ring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-0&lt;/span&gt; &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-offset-0&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;Now, instead of re-defining these utilities on all of my inputs, I can use the &lt;code&gt;without-ring&lt;/code&gt; class to achieve the desired output:&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;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;className=&lt;/span&gt;&lt;span class="s"&gt;"without-ring"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Keeping it accessible
&lt;/h2&gt;

&lt;p&gt;Accessibility is important, but to keep it short, I won’t cover it here. Instead, &lt;a href="https://medium.com/hackernoon/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2"&gt;this post by David Gilbertson&lt;/a&gt; provides a simple, framework-agnostic solution to showing the focus ring only when a user is tabbing and making it hidden otherwise.&lt;/p&gt;

&lt;p&gt;Keeping it within Tailwind, we can use both our pre-defined without-ring class and native CSS selectors to show the ring for keyboard users, but hide it when using a mouse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.without-ring&lt;/span&gt;&lt;span class="nd"&gt;:focus:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-0&lt;/span&gt; &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-offset-0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.without-ring&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;ring-2&lt;/span&gt; &lt;span class="err"&gt;ring-offset-2;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nt"&gt;If&lt;/span&gt; &lt;span class="nt"&gt;we&lt;/span&gt; &lt;span class="nt"&gt;wanted&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="nt"&gt;apply&lt;/span&gt; &lt;span class="nt"&gt;this&lt;/span&gt; &lt;span class="nt"&gt;globally&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;we&lt;/span&gt; &lt;span class="nt"&gt;could&lt;/span&gt; &lt;span class="nt"&gt;use&lt;/span&gt; &lt;span class="nt"&gt;native&lt;/span&gt; &lt;span class="nt"&gt;type&lt;/span&gt; &lt;span class="nt"&gt;selectors&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:focus:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-0&lt;/span&gt; &lt;span class="n"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ring-offset-0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"checkbox"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nd"&gt;:focus-visible&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;ring-2&lt;/span&gt; &lt;span class="err"&gt;ring-offset-2;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>tailwindcss</category>
      <category>css</category>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Cherry-picking done easy</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Thu, 27 Jan 2022 19:05:06 +0000</pubDate>
      <link>https://forem.com/romansorin/cherry-picking-done-easy-2h2a</link>
      <guid>https://forem.com/romansorin/cherry-picking-done-easy-2h2a</guid>
      <description>&lt;p&gt;Have you ever needed to cherry-pick several commits between two branches, but ran into conflicts every time? I made this mistake myself – but cherry-picking doesn’t have to be hard.&lt;/p&gt;

&lt;p&gt;As you work on more features and fix more issues, some might not be released immediately. When moving code between environments, you may need to move specific changes over, even if they live alongside other things that you’ve written. This is where cherry-picking comes into play, and if you’ve ever worked in any type of git-flow, you’ve probably come across it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategies to the rescue
&lt;/h2&gt;

&lt;p&gt;Merge strategies allow you to define how to apply commits from one branch to another. By default, the cherry-pick strategy is &lt;a href="https://git-scm.com/docs/git-merge#Documentation/git-merge.txt-recursive"&gt;recursive&lt;/a&gt;. Depending on what base branch you’re working off of, you’ll want to use the correct merge strategy for specifying what changes to use: this is where &lt;code&gt;-X=theirs&lt;/code&gt; and &lt;code&gt;-X=ours&lt;/code&gt; do us favors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performing the cherry-pick
&lt;/h2&gt;

&lt;p&gt;The cherry-pick command is simple:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git cherry-pick --strategy=recursive -X=theirs hash0 hash1 hash2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’re able to specify the strategy, the per-strategy option (theirs/ours), and the sequence of hashes (commits) to merge.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is the difference between &lt;em&gt;theirs&lt;/em&gt; and &lt;em&gt;ours&lt;/em&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This option will specify which version of code to prefer in a merge. If you’re on the main branch and moving commits from develop, you’ll want to use &lt;em&gt;theirs&lt;/em&gt;, as you are saying “let’s take develop’s (their) changes”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you’ll need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A base and target branch (develop/main)&lt;/li&gt;
&lt;li&gt;The commit hashes to cherry-pick (e.g. &lt;code&gt;7f7a499&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Choosing the right strategy based on what you want to achieve (ours/theirs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to merge a sequence of commits from a development branch into main:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check out the main branch with &lt;code&gt;git checkout main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Using the cherry-pick command from above, we will merge several develop commits to our main branch, moving the develop changes onto main. You can specify the hashes individually, or you can cherry-pick a sequence of hashes by writing &lt;code&gt;hash0...hash1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If conflicts come up, you’ll be able to resolve them on a per-commit basis and commit the result to continue with your cherry-picking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Although simple, learning this has turned large merge requests that affected over 40 files (including deletions, renames, and refactors) into a pain-free experience instead of a major ordeal. Something that would’ve taken me several hours with the potential of missing code only took an hour, and shortly thereafter, I was able to move on to working on my next task.&lt;/p&gt;

&lt;p&gt;This isn’t all that cherry-pick has to offer, and there are flags for you to provide based on your use case. If you need something beyond this or if you want to read a little bit about how cherry-picks are done, the &lt;a href="https://git-scm.com/docs/git-cherry-pick"&gt;Git docs&lt;/a&gt; do a great job of explaining this concept.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Impact of Layout on User Experience: A Study</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Wed, 01 Dec 2021 22:14:49 +0000</pubDate>
      <link>https://forem.com/romansorin/the-impact-of-layout-on-user-experience-a-study-1na5</link>
      <guid>https://forem.com/romansorin/the-impact-of-layout-on-user-experience-a-study-1na5</guid>
      <description>&lt;p&gt;A year ago, I wrote a research paper titled &lt;strong&gt;&lt;a href="https://sorinportfolio.s3.us-east-2.amazonaws.com/Identification+and+Measurement+of+Hierarchical+Layout+Patterns+on+User+Experience+-+Roman+Sorin.pdf"&gt;Identification and Measurement of Hierarchical Layout Patterns on User Experience&lt;/a&gt;&lt;/strong&gt;. This paper documents a study that spanned over several months (September of 2019 until April of 2020), in which I performed a two-part analysis &amp;amp; experiment on the most popular site layouts and how user experience is affected. While this paper barely scratched the surface of what is possible, it documented a novel approach to studying user experience and how it may be possible to measure "success" through objective metrics — both quantitative and qualitative. Below is the abstract of that paper and a link to continue reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;User experience continues to grow in importance as web applications become increasingly widespread and utilized. Further, high-quality user experience often leads to increased user retention, product usage, and revenue for businesses and applications alike. Though user experience is a focus for some, there is often ambiguity in what an optimal or effective layout looks like, and how components within the hierarchy should be structured to accomplish a set goal. This study proposes a potential approach to objectively analyzing and identifying layouts from the highest indexing sites on a global scale, and methods of implementation to assess whether or not certain metrics representing user experience are impacted by layout. After analyzing 908 screenshots and four clusters from the globally highest ranking sites using a K-means algorithm and weighted subclustered images, three common layouts, or variants, were identified and created for use in a web application. Users were randomly assigned one of three layouts, and they were instructed to navigate through the layout to register or purchase the product while their session lengths and mouse movements were recorded. The results from this study found that while the differences in variances between the mean session lengths from 55 participants were statistically insignificant, interesting patterns were found from a manual qualitative analysis when comparing interface hierarchies to data. Subsequently, it was found that various avenues exist for improvement of the model, such as an objective, mathematical metric to understand how layout impacts user experience metrics.&lt;/p&gt;

&lt;p&gt;Read the paper &lt;a href="https://sorinportfolio.s3.us-east-2.amazonaws.com/Identification+and+Measurement+of+Hierarchical+Layout+Patterns+on+User+Experience+-+Roman+Sorin.pdf"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More information
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Title:&lt;/strong&gt; Identification and Measurement of Hierarchical Layout Patterns on User Experience&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Written:&lt;/strong&gt; April 22, 2020&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keywords:&lt;/strong&gt; user experience (UX), K-means clustering, Keras VGG16, AWS Alexa&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How do you like to read articles/tutorials?</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Mon, 22 Nov 2021 14:20:19 +0000</pubDate>
      <link>https://forem.com/romansorin/how-do-you-like-to-read-articlestutorials-1k3l</link>
      <guid>https://forem.com/romansorin/how-do-you-like-to-read-articlestutorials-1k3l</guid>
      <description>&lt;p&gt;When reading documentation or articles that contain code, do your prefer to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;step through the file line-by-line&lt;/li&gt;
&lt;li&gt;see the full code snippet pasted with inlined comments&lt;/li&gt;
&lt;li&gt;see the full snippet, and a summary/walk-through after?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's an example of #2/3 taken from a recent post of mine:&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;import&lt;/span&gt; &lt;span class="nx"&gt;hashids&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;hashids&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Two arguments supplied: a salt and a minimum padding (length)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedPostId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 6akz1JVq&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Here, we're importing the hashids package and creating an instance of the module, calling it postHash. ..."&lt;/p&gt;

&lt;p&gt;vs. doing something like:&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;postHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"First, we create an instance of the hashids module and supply a salt and padding. ..."&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Hiding primary keys and database IDs using Hashids</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Mon, 22 Nov 2021 13:07:51 +0000</pubDate>
      <link>https://forem.com/romansorin/hiding-primary-keys-and-database-ids-using-hashids-2ida</link>
      <guid>https://forem.com/romansorin/hiding-primary-keys-and-database-ids-using-hashids-2ida</guid>
      <description>&lt;p&gt;In most CRUD operations and REST APIs, primary keys are used to reference models that you want to access or modify. A majority of APIs will take an ID as a parameter in a route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/v1/posts/:id

// Return the Post resource with an ID of 457
GET /api/v1/posts/457
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it's the simplest and most effective way of specifying the model to use, we often don't want to show these IDs to the user. By displaying primary keys, you give users the ability to estimate the number of rows in your tables. If authorization isn't effective or routes aren't protected, users could input random numbers to access information that they otherwise shouldn't have.&lt;/p&gt;

&lt;p&gt;Using obfuscated IDs can be useful in social media or feed contexts, where the content isn't used in the URL, but you want something less significant than a primary key. As an example, instead of showing the user a URL like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://romansorin.com/posts/457&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We may want to show them something like this instead:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://romansorin.com/posts/akz1JV&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this case, you may want to use "hashes" as a way to obfuscate your ID. We'll use the lightweight &lt;a href="https://hashids.org/"&gt;Hashids&lt;/a&gt; package to make this happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Getting started with Hashids is simple. With your preferred package manager, add Hashids to your project:&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;# If you use yarn as a package manager&lt;/span&gt;
yarn add hashids

&lt;span class="c"&gt;# Or if you use npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;hashids
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;I've provided a Javascript example to begin working with Hashids, but Hashids has &lt;a href="https://hashids.org/"&gt;support for several languages&lt;/a&gt;!&lt;br&gt;
Here's a brief use case, where you may want to hide the ID of a post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;hashids&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;hashids&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Two arguments supplied: a salt and a minimum padding (length)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedPostId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 6akz1JVq&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we're importing the &lt;code&gt;hashids&lt;/code&gt; package and creating an instance of the module, calling it &lt;code&gt;postHash&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I set up a dummy post object, but you can use any object or ID that you see fit. Due to package limitations, the argument supplied to the module &lt;em&gt;must be an integer&lt;/em&gt;. Strings and objects cannot be obfuscated using Hashids.&lt;/p&gt;

&lt;p&gt;Afterward, I supplied the ID into the &lt;code&gt;encode&lt;/code&gt; function of the &lt;code&gt;postHash&lt;/code&gt; object and then put this output back through the &lt;code&gt;decode&lt;/code&gt; function to show how you can use encoding/decoding. Note that the return type of &lt;code&gt;decode&lt;/code&gt; is an array, &lt;em&gt;not&lt;/em&gt; an integer.&lt;/p&gt;

&lt;p&gt;If that's all you're looking for, then that's it! You can also encode and decode multiple IDs at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&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;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: A utility class
&lt;/h2&gt;

&lt;p&gt;If you want a common utility to work with, &lt;a href="https://gist.github.com/romansorin/c25e8cbe132ae34bf19ee91e5926a364"&gt;here is an abstraction&lt;/a&gt; on top of the Hashids package that will allow you to encode and decode IDs easily, without having to remember the package's methods.&lt;/p&gt;

&lt;p&gt;This class is limited to encoding/decoding a single ID at a time but it helps me stay consistent within my projects. By using this utility, you could also set up a file/store of your hash objects, so you don't have to redefine it across your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/Hash.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Hashids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hashids/cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Creates a Hash object.
   *
   * @param {string} salt The unique salt/alphabet to use for salting. Setting a salt allows output hashes to be more unique.
   * @param {number} padding The minimum output length of the hash (default is 6).
   */&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Encodes the provided ID argument and returns a string representing the hash.
   *
   * @param {number} id The numeric "id" to be encoded or turned into a hash.
   * @returns {string} Returns the encoded ID in the form of a hash, e.g. "o2fXhV"
   */&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;encodeId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Decodes the provided hash argument and returns a number representing the ID.
   *
   * @param {string} id The numeric "id" to be encoded or turned into a hash.
   * @returns {number} Returns the numeric ID, e.g. "1"
   */&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;decodeId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&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;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decoded&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="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Sets the internal hashids object with the provided salt/padding arguments.
   *
   * @param {string} salt The unique salt/alphabet to use for salting. Setting a salt allows output hashes to be more unique.
   * @param {number} padding The minimum output length of the hash (default is 6).
   */&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;setHashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hashids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&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;Using this utility class is as simple as the native Hashids package. The implementation stays largely the same, but may be more readable and easy to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@lib/Hash&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@app/models/Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new Hash object with the salt "post"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// We may want to generate different sequences based on model, to get different values for the same ID&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedPostId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 6akz1JVq&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decodeId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;

&lt;span class="c1"&gt;// Want to change the salt of the Hash object without creating a new object?&lt;/span&gt;
&lt;span class="c1"&gt;// Call "setHashids" through the utility function.&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHashids&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;postHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Now, it returns undefined instead of 4&lt;/span&gt;

&lt;span class="c1"&gt;// With a different salt, we can use the old Post ID and get a different value:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashedUserId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;hashedPostId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// dD0WnjRy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example is a little bit more extensive, so let me walk you through it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We created two hash objects to represent a Post and User model.&lt;/li&gt;
&lt;li&gt;Like the previous example, I created a dummy Post object with an ID of 4.&lt;/li&gt;
&lt;li&gt;I passed the ID into the encode function (of the custom utility) and then decoded it, which was the same as the previous example.&lt;/li&gt;
&lt;li&gt;The utility allows you to set a new salt and padding within the same object instance, so I changed the salt to "comment". Now, when you try to decode the previous hash, you don't get the same ID.&lt;/li&gt;
&lt;li&gt;Since the &lt;code&gt;userHash&lt;/code&gt; object had a different salt, encoding the previous ID returns a completely different hash.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, a limitation of this utility is that you can't encode or decode multiple IDs at once, but this can be easily added in by extending the class functions. When developing a medium-scale app with the Hashids library, I found this utility to be super useful in keeping my code consistent across controllers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;It's worth noting that Hashids &lt;strong&gt;should not&lt;/strong&gt; be used as a legitimate hashing solution (such as passwords or other encrypted information). The Hashids package doesn't have support for strings anyway, but you shouldn't even consider this – instead, use an algorithm like &lt;a href="https://auth0.com/blog/hashing-in-action-understanding-bcrypt/"&gt;bcrypt&lt;/a&gt; to encrypt your sensitive data.&lt;/p&gt;

&lt;p&gt;Additionally, as the package creator describes, these aren't true "hashes". Cryptographic hashes cannot be decrypted, but the output looks similar which results in this obfuscation being considered a "hash".&lt;/p&gt;

&lt;h2&gt;
  
  
  More robust strategies
&lt;/h2&gt;

&lt;p&gt;When I was looking into packages and solutions to masking IDs in an application of my own, my first thought was to look into what companies like &lt;a href="https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c"&gt;Instagram&lt;/a&gt; and &lt;a href="https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake"&gt;Twitter&lt;/a&gt; were doing. I noticed that despite the volume of data that is processed on these platforms, they didn't resort to using primary keys for their URLs. If you're interested in how they handled this ID generation (hint: it wasn't Hashids!), I would highly suggest reading the articles I linked above.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.notion.so/Hiding-primary-keys-and-database-IDs-using-Hashids-7257783fd3e44ad6acf3914c25cac910"&gt;Medium post&lt;/a&gt; documenting Instagram's solution, the URL contains yet another example of hashes being used in the URL: first the slug of the article, and then a sequence of random characters after to maintain uniqueness.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In a content-heavy context where the title of a post may be significant (blogs or forums), this approach keeps the URL significant but also minimizes the chance of collisions by keeping records unique.&lt;/p&gt;

&lt;p&gt;Hashids is an effective approach for a small to medium-scale application that doesn't require more complicated strategies like combining different metadata (creation date, worker/sequence count, shard IDs). While it doesn't fit data or scale-intensive cases like Twitter, regular applications that process a reasonable amount of writes will do just fine with this approach. Collisions can be avoided by choosing unique salts for each model that you obfuscate and an appropriate minimum length (at least 8 characters).&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
      <category>performance</category>
    </item>
    <item>
      <title>Sticky Footers</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Mon, 15 Nov 2021 13:26:48 +0000</pubDate>
      <link>https://forem.com/romansorin/sticky-footers-4122</link>
      <guid>https://forem.com/romansorin/sticky-footers-4122</guid>
      <description>&lt;p&gt;In most of my apps, I almost always want the footer at the bottom of the page.&lt;/p&gt;

&lt;p&gt;This is really easy to accomplish: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;first, we make the actual page at least the height of the screen&lt;/li&gt;
&lt;li&gt;then, we move the footer to the bottom of the page with auto margin if there isn't enough natural content&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  With CSS
&lt;/h3&gt;

&lt;p&gt;With a simple page like this,&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;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        ... other content ...
      &lt;span class="nt"&gt;&amp;lt;footer&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;our CSS looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nf"&gt;#app&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&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;
  
  
  With Tailwind
&lt;/h3&gt;

&lt;p&gt;Tailwind makes it even easier.&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;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen flex flex-col"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ... other content ...
    &lt;span class="nt"&gt;&amp;lt;footer&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mt-auto"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Zillow's confusing keyword UX</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Wed, 27 Oct 2021 02:20:14 +0000</pubDate>
      <link>https://forem.com/romansorin/zillows-confusing-keyword-ux-3k38</link>
      <guid>https://forem.com/romansorin/zillows-confusing-keyword-ux-3k38</guid>
      <description>&lt;p&gt;&lt;a href="https://zillow.com"&gt;Zillow&lt;/a&gt; has consistently been one of the most popular real estate marketplaces for several years, and for good reason — Zillow has dominated the space for buying and selling homes, as well as listing rentals and providing home loans for prospective buyers. While searching for apartments this summer (and as I'm planning to buy a home), I used Zillow almost exclusively to explore options and contact properties.&lt;/p&gt;

&lt;p&gt;Almost &lt;a href="https://www.nar.realtor/sites/default/files/documents/2019-profile-of-home-buyers-and-sellers-highlights-11-21-2019.pdf"&gt;half of all people&lt;/a&gt; searching for homes start by looking online before contacting a local realtor. Despite the amount of information that searchers have immediately at their disposal, finding a property is nothing like a product on Amazon. Homebuyers using Zillow and similar platforms spent, on average, ten weeks searching for homes; those who took traditional routes spent closer to four weeks.&lt;/p&gt;

&lt;p&gt;This brings us to one of the key features in any search or commerce context: filtering. For being such a large name in the real estate space, it's hard to believe that the options for filtering are so primitive. Filtering is limited to specifying a price range, beds/baths, and other basic features, but almost nothing more. As you use the platform, you have to potentially spend hours trying to find properties that fit your criteria. While this increases engagement, you end up frustrating the user.&lt;/p&gt;

&lt;p&gt;Intuitively, you would expect the "keywords" field to give you more control of what you're looking for. The ability to specify certain words or phrases is convenient, especially when you're looking for a certain style of home or other qualitative traits beyond the generics. With so much information attached to listings, it's surprising that there isn't a more developed way of finding this deeper engrained yet relevant information.&lt;/p&gt;

&lt;p&gt;With this, let's explore the keywords field: what it gets right, how it can improve, and how other platforms can change the approach to home searching.&lt;/p&gt;

&lt;h2&gt;
  
  
  What they get right
&lt;/h2&gt;

&lt;p&gt;From the input, it's clear that they expect you to input keywords as a comma-delimited list. After you've finished and clicked "Done", your list of results decreases. This is expected behavior but doesn't offer much feedback. Zillow isn't alone in this behavior – another well-known marketplace, &lt;a href="https://redfin.com"&gt;Redfin&lt;/a&gt;, also has the same "keywords" field that functions similarly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54dkyjer9ursm580obsc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54dkyjer9ursm580obsc.png" alt="Zillow's keywords field" width="537" height="267"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The keywords input is clear as it's comma-delimited and gives examples of what it's looking for.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Without trying several cases to see how the search performs, it's difficult to see how the keywords field works. After trying several sequences of keywords ("open", "open, modern", "open, modern, pool"), I found that it matches each keyword and works as a strict filter, rather than evaluating each keyword individually.&lt;/p&gt;

&lt;p&gt;This works, and once you understand it, functions as expected. But it's kind of useless.&lt;/p&gt;
&lt;h2&gt;
  
  
  Complex queries aren't possible
&lt;/h2&gt;

&lt;p&gt;To start, I made a baseline search around Dallas, TX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price Range: $400k-$1m
Home Type: Single-family, for sale
3+ beds, 2+ baths

Results: 5,964 listings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This yielded 5,964 listings which is a healthy number to filter down. At this point, &lt;em&gt;any&lt;/em&gt; home matching these criteria will be returned and doesn't take other features into account.&lt;/p&gt;

&lt;p&gt;As mentioned before, I'm using a combination of phrases such as "open, modern, pool" to evaluate how filtering works with this field and what it potentially misses.&lt;/p&gt;

&lt;p&gt;Here are the results of using different combinations of these phrases to illustrate how big of an impact the keywords field has on search results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listings by keyword:

"Open": 2,749
"Pool": 1,273
"Open, Pool": 502
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single keyword significantly reduces the number of results and hopefully can filter out non-matching properties. However, this field is only useful if the information is available and it takes the &lt;em&gt;correct&lt;/em&gt; information into account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildly inaccurate
&lt;/h2&gt;

&lt;p&gt;With a little bit more investigation on my baseline query, I decided I'd try to test the accuracy of the keywords field. As an example, I entered "3 bed" into the field on its own, which reduced the number of results to 162 listings. This seems somewhat promising, yet my first result was a four-bedroom house. Even if I'm allowing more than three bedrooms in my base query, properties matching the keyword should be returned first. What gives?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k0wx6d7lu4w9s2i8asg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0k0wx6d7lu4w9s2i8asg.png" alt='Keywords field with "3 bed" specified' width="800" height="538"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Just supplying "3 bed" as the phrase for the keywords returns 162 listings, opposed to the original 5,964.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I looked throughout the listing to find any mention of "3 bed" or the number 3 at all. All I could find was the "3 CAR GARAGE" tag, which is not even contextually relevant.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxzquoypt4qkrhidlljq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxzquoypt4qkrhidlljq.png" alt='A listing found under the "3 bed" keyword, but there is no mention of 3 bed on the listing itself' width="800" height="574"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;With "3 bed" in the keywords input, this listing is returned — but the description has no mention of "3 beds" (only "3 car garage"), and the house itself has 4 bedrooms.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I looked to see what the sorting options were for results to see if there was a "Relevance" option. Nothing would change the results appropriately to what I wanted, despite being a very basic filter. The nearest option is "Homes for You", which is supposedly determined based on the homes you've viewed and your prior searches, but I don't want a feed — I want better search results. Yes, there is a dedicated filter functionality for choosing the number of beds you want to have returned, but this result speaks volumes on the limitations of filtering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffid5i5qxop6hwdks5vhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffid5i5qxop6hwdks5vhp.png" alt='A dropdown of the listing results sorting options, with "Homes for You" selected' width="727" height="339"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Zillow offers different types of sorting options, but nothing to do with search relevancy beyond "Homes for You".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's evident that when the filter does work, it seems to only search the description of the listing. This leaves out a lot of valuable information, especially if the description is incomplete, or if the relevant data is in some other property of the listing. Despite the number of attributes that can be filled out for a listing, these are very loosely compared against the keywords input.&lt;/p&gt;

&lt;p&gt;For final verification of the attributes that are likely considered, I searched the "Parcel Number" that was found under a property listing in the keywords (Zillow gives an example of "MLS #" in their input, so I figured other IDs would be supported), and that listing could no longer be found. While I don't know how their data is modeled, I believe they could instead do a more exhaustive, comprehensive search over the listing with proper indexing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better way of home searching
&lt;/h2&gt;

&lt;p&gt;From testing, it seems that keywords are all evaluated as a strict filter, meaning that they are matched directly against. I didn't see support for fuzzy text searching (i.e. misspelling a keyword may return zero results), and the "3 bed" experiment showed that there is no contextual relevancy.&lt;/p&gt;

&lt;p&gt;On Zillow and other platforms, it would be nice if you could input a more generic search term or phrase that allowed for more specificity. There may not be a huge market need for it, but it could potentially be a major help to people who want to find homes that are aligned with their needs, without having to reach out to realtors and agents. I haven't come across a real estate listing site that operates as a search engine – while it's a lot to ask, it would offer homebuyers a faster path to finding candidate homes that they might want.&lt;/p&gt;

&lt;p&gt;Aside from working with a realtor, it can be tough to find exactly what you want without spending a lot of time looking at options. Even if you're casually browsing, it can be frustrating to have a massive list of potentially irrelevant options shown to you, especially if you know what you're looking for.&lt;/p&gt;

&lt;p&gt;I briefly explored this issue earlier in the year while developing an MVP for a small-time project of mine called &lt;a href="https://abodelistings.com"&gt;Abode&lt;/a&gt;. The model for home searching has been consistent for years, but that doesn't necessarily mean it works well, especially as usage patterns evolve.&lt;/p&gt;

&lt;p&gt;While drafting ideas on how to solve some of these problems, I came across an &lt;a href="https://www.zillow.com/tech/improve-quality-listing-text/"&gt;article written by Zillow&lt;/a&gt; which explored improving results by putting the listing's description through an embedding model paired with existing recommender systems. The article states that Zillow uses different features of the listing "to build recommendations on similar homes, personalized search rankings, and other personalization systems for customers", so it seems that much of this recommendation is done behind the scenes, without the user being aware of it. It's a bit ironic that their behind-the-scenes systems seem to be well-thought-out, but their search functionality is so primitive – despite being the point of entry for users looking to find a home.&lt;/p&gt;

&lt;p&gt;It seems like &lt;a href="https://www.zillow.com/tech/personalized-search-refinements/"&gt;Zillow is working on solutions&lt;/a&gt; to solve this through their existing filters, which looks to be based on localized demand (on-site parking, allowing pets in complexes, etc.) – but still haven't offered a solution to more "fuzzy" suggestion. Using embeddings and making predictions based on provided phrases seems like a great solution to improving search quality and allowing for a more attractive search process. While I'm doubtful Zillow will solve this problem themselves, it could be an attractive offering if a new platform presents itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  But is it necessary?
&lt;/h2&gt;

&lt;p&gt;Admittedly, it might not be solving all that large of a problem. After looking at various results on Google Trends, the most popular searches are things like "new construction houses for sale", "beach houses for sale", "manufacturer homes" — results which are easily returned through the dedicated filters. But, if a property listing is incomplete beyond the basic information and a description, information that is important to some people (such as the "view" or certain amenities) may be lost. People searching for apartments, for example, may input several criteria into the keywords field, but don't expect them all to be matched. Even if they do, too many keywords may return no results. Apps like &lt;a href="https://www.apartmentlist.com/"&gt;ApartmentList&lt;/a&gt; have solved some of these issues, but it's less known than Zillow and other platforms.&lt;/p&gt;

&lt;p&gt;Real estate listing sites and aggregators are difficult to do, in some respects. You can't always have perfect information, especially at scale — but there are ways of solving some of the limitations. The root of this problem seems to be the incompleteness of data. In the future, I would love to see a real estate platform use more advanced concepts, such as &lt;a href="https://en.wikipedia.org/wiki/Recurrent_neural_network"&gt;RNNs&lt;/a&gt;/&lt;a href="https://en.wikipedia.org/wiki/Convolutional_neural_network"&gt;CNNs&lt;/a&gt;, to automatically extract features from photos to pre-fill or supplement listings. Building extra layers such as checking image quality, description quality, and providing feedback or predictions on how well a listing may perform may ultimately ease frustrations for a homebuyer.&lt;/p&gt;

</description>
      <category>design</category>
      <category>ux</category>
      <category>webdev</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Design for your developers, not just your users</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Sat, 16 Oct 2021 14:15:35 +0000</pubDate>
      <link>https://forem.com/romansorin/design-for-your-developers-not-just-your-users-20bk</link>
      <guid>https://forem.com/romansorin/design-for-your-developers-not-just-your-users-20bk</guid>
      <description>&lt;p&gt;As a developer, I have worked on many feature definitions, tickets, and mockups. Working on both freelance projects and at companies, I have come across unclear requirements more times than I can count. Though it's impossible to be perfect, there are ways to improve developer productivity and establish a better foundation for how your apps are built.&lt;/p&gt;

&lt;h3&gt;
  
  
  It starts by designing for your developers.
&lt;/h3&gt;

&lt;p&gt;Though users are typically the primary stakeholders, recognize that developers are the bridge between concept and execution. Developers are limited by similar factors; time and budget being two of the most common. However, some limitations are entirely within a designer's control.&lt;/p&gt;

&lt;p&gt;For example, designs that are overly complex or ambitious can be a nightmare for developers. Even if the design looks eye-catching (think most &lt;a href="https://dribbble.com"&gt;Dribbble&lt;/a&gt; shots), it might simply be too difficult to implement or even use. Similarly, developers can perform higher quality work at a much faster rate if there doesn't need to be a constant feedback-support loop. A developer shouldn't have to constantly ask what should be displayed in some input case (think failure/success handling), or why two elements are inconsistent in design despite supposedly being the same.&lt;/p&gt;

&lt;p&gt;Here are some basic but common examples of design states that are often forgotten:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Null/empty values&lt;/li&gt;
&lt;li&gt;New values (reassigning from null)&lt;/li&gt;
&lt;li&gt;In progress, loading, or pending states&lt;/li&gt;
&lt;li&gt;Error, success, etc. Should these be messages? Other visual cues?&lt;/li&gt;
&lt;li&gt;Consistency in implementation, such as text size and color, padding, grid spacing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While developers should be responsible for making reasonable assumptions and maintaining autonomy, their job is to produce a fluid, functional application from what's provided. If given something difficult to work with, there's no guarantee that the actual app will be any better.&lt;/p&gt;

&lt;p&gt;Confusion breeds chaos and makes it tough for many devs – especially those who have little interest or background in design – to accomplish their work in the expected amount of time. For an application to be well-executed, all three points need to interact and consider each other's needs: designers, developers, and users.&lt;/p&gt;

&lt;p&gt;As a designer, you’re probably dealing with different sets of requirements, communication across different departments, and responsible for designing flows. You may be constrained by deadlines or financial limitations, and so it’s tough. But keep developers in mind, and they’ll return the favor through a beautifully executed user experience.&lt;/p&gt;




&lt;p&gt;In exploring this idea, I found that Invision's &lt;em&gt;Inside Design&lt;/em&gt; &lt;a href="https://www.invisionapp.com/inside-design/design-for-development/"&gt;published an article&lt;/a&gt; that covered a similar idea. It's more in-depth and put together than this article, and I highly suggest you read it if this idea interests you.&lt;/p&gt;

&lt;p&gt;You can find more of my content &lt;a href="https://romansorin.com/blog"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>design</category>
      <category>startup</category>
      <category>ux</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My experience of moving from Laravel to Django</title>
      <dc:creator>Roman Sorin</dc:creator>
      <pubDate>Sat, 09 Oct 2021 18:10:49 +0000</pubDate>
      <link>https://forem.com/romansorin/my-experience-of-moving-from-laravel-to-django-3p7n</link>
      <guid>https://forem.com/romansorin/my-experience-of-moving-from-laravel-to-django-3p7n</guid>
      <description>&lt;p&gt;Before my current role, I spent most of my professional development career writing web apps in &lt;a href="https://laravel.com/"&gt;Laravel&lt;/a&gt;. I learned Laravel out of necessity after learning PHP, but wanted to avoid working in WordPress – and I quickly grew fond of the framework. Even during Laravel 5, the framework just felt right.&lt;/p&gt;

&lt;p&gt;It was highly opinionated (which was crucial as a new developer, trying to learn the best way to structure large scale, multi-faceted apps), had robust documentation that was extremely easy to follow, and had very clear design patterns that made it easy to interface with the ORM and concepts like Facades. When scaffolding out a new Laravel project, namespaces were clear and it was easy to see how different parts of the app interacted with each other. Laravel's use of &lt;a href="https://laravel.com/docs/8.x/artisan"&gt;Artisan&lt;/a&gt; was also a major developer experience boost – you could generate new controllers, models, notifications, and more – without having to rely on any other external tools or questioning if it's structured properly. It just &lt;em&gt;worked&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Later on, I moved to using Django as my primary framework. I figured that the transition between Laravel to Django would be simple, but even some of the more basic concepts gave me trouble regarding terminology and patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Django?
&lt;/h2&gt;

&lt;p&gt;Whenever I was looking for new roles at the beginning of 2021, I ended up interviewing for a company (where I currently work) that was using Django as a large part of its stack. Consistently, I was seeing more opportunities for Django and other frameworks revolving around Python and felt that I may have been inhibiting my growth by staying on the Laravel track.&lt;/p&gt;

&lt;p&gt;Even though I had used Flask before in several freelance projects, Django felt like a completely different beast. Flask touts itself as a "micro-framework" and compared to full-fledged frameworks like Django or Laravel, that's exactly what it is. It's very convenient for creating more directed projects quickly but falls short with tooling.&lt;/p&gt;

&lt;p&gt;I had a preliminary interview and immediately began learning Django. Despite making it clear that I hadn't used Django before, I wanted to get as much knowledge of the framework as possible so that I wouldn't be subject to a major learning curve before beginning my new position.&lt;/p&gt;

&lt;p&gt;I didn't want to put all of my eggs into one basket, so I kept the sample project I was working on small. It had to be just enough that I covered the major concepts of Django, but with enough complexity that it couldn't be classified as a "to-do list". At the time, I wanted a way of tracking inventory in my home office, so I built an incomplete &lt;a href="https://github.com/romansorin/inventory-system"&gt;inventory tracking system&lt;/a&gt; after following the Django docs. Despite having a &lt;em&gt;lot&lt;/em&gt; more experience with the framework now, I still reference this project sometimes when I'm implementing new viewsets (more on this later).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; If you really want to work at a company and don't have a solid grasp on the tech that they're using, pour your time into it (within reason). While it's perfectly okay to learn on the job (as I did, and still do), being able to hit the ground running and contribute is personally incredibly rewarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parallels between Laravel and Django
&lt;/h2&gt;

&lt;p&gt;While this won't be a detailed technical writeup of the two, there are some core concepts that exist in parallel between Laravel and Django. To start, Laravel and Django both follow the &lt;a href="http://www.cs.utsa.edu/~cs3443/mvc-example.html"&gt;Model-view-controller (MVC)&lt;/a&gt; design pattern, which is a common way of developing both desktop and web applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Freedom to choose
&lt;/h3&gt;

&lt;p&gt;Though they both follow MVC, Laravel feels more "strict" in enforcing certain patterns. If you've ever taken a look at the &lt;a href="https://laravel.com/docs/master"&gt;Laravel documentation&lt;/a&gt; or inside of a Laravel project, you'll see that there are a lot of different features of the framework that are already abstracted out for you. At a minimum, you run &lt;code&gt;php artisan&lt;/code&gt; and generate the components that you need – at maximum, you have to do some configuration to make it fit eloquently into the rest of your app. I use Laravel's documentation as a gold standard in framework + technical documentation and wish more would follow suit.&lt;/p&gt;

&lt;p&gt;Contrarily, I have always felt that Django gives you a bit more freedom to make your own decisions on how you want to structure your app, what namespaces (module directories) to use, etc. &lt;a href="https://docs.djangoproject.com/en/dev/"&gt;Django's documentation&lt;/a&gt; isn't fantastic compared to Laravel, but it's enough that you can reference and implement different concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an API
&lt;/h3&gt;

&lt;p&gt;Django also provides you with an "admin" view, which is essentially a CRUD panel for any models that you load. This is probably a better choice for someone looking for a framework with an admin dash prebuilt, but I use both Laravel and Django in the context of building an API only. With Django, you'd need to pull in &lt;a href="https://www.django-rest-framework.org/"&gt;Django REST framework (DRF)&lt;/a&gt; – Laravel offers it right out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tinkering with data
&lt;/h3&gt;

&lt;p&gt;Both Laravel and Django have a CLI tool that allows you to use parts of your app in isolation and interact with the database. This is really useful when you want to see what certain methods of the query builders do, or one-off functions that would take more writing in a controller/viewset. I personally feel that &lt;code&gt;php artisan tinker&lt;/code&gt; is easier to use and integrated more closely with the framework. It's an incredibly tiny detail, but tinker utilizes autoloading and allows you to reference models by namespaces, without having to import them. This is different from the Django &lt;code&gt;manage.py shell&lt;/code&gt;, where you have to import all of the modules and classes that you want to interact with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where my confusion lied
&lt;/h2&gt;

&lt;p&gt;Following the MVC pattern, Laravel and Django both have models, views, and controllers. However, I felt that Django was a lot less clear in designating what maps to what, at least coming from a traditional background. Let me explain:&lt;/p&gt;

&lt;h3&gt;
  
  
  Laravel
&lt;/h3&gt;

&lt;p&gt;Two majors concepts of building APIs in Laravel are models and controllers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://laravel.com/docs/8.x/eloquent"&gt;A blueprint for objects&lt;/a&gt;, which is just a class that allows you to specify a different table name, primary key, attributes, etc. Unlike Django, defining fields is abstracted away to migration, and Laravel separates the Eloquent instance from the database representation.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// App\Models\Employee.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Database\Eloquent\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Controller:&lt;/strong&gt; Typically, &lt;a href="https://laravel.com/docs/8.x/controllers"&gt;controllers return model instances&lt;/a&gt; with minimal processing. These are where you handle defining CRUD operations for your endpoints (web or API). Unlike Django's optional functional approach, these are always classes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// App\Http\Controllers\API\EmployeeController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers\API&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Controllers\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\Employee&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;findOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&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;ul&gt;
&lt;li&gt;
&lt;strong&gt;Views:&lt;/strong&gt; &lt;a href="https://laravel.com/docs/8.x/views"&gt;Laravel views&lt;/a&gt; are Blade templates that are separated from your business logic, and only hold the presentation layer. These are used to display data provided from controllers – the use of "views" is going to be an important distinction in Django, where these are called "templates" in a Django context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When defining an API route, you only need to reference the method of an API controller. You don't need to touch "views" at all, and in a lot of my projects, I never created a single view (I used a separate React/Vue app for this).&lt;/p&gt;

&lt;h3&gt;
  
  
  Django
&lt;/h3&gt;

&lt;p&gt;On the other hand, Django uses the concepts of models, serializers, and views (which can be called viewsets). Even for API routes, all three of these are required. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; &lt;a href="https://docs.djangoproject.com/en/3.2/topics/db/models/"&gt;The model&lt;/a&gt; is similar to Laravel's definition, but must contain the fields and metadata at a minimum. If you want to define any custom attributes or model-specific business logic, this must be done in a serializer. On one hand, it's easier to follow and practices better separation of concerns – on the other hand, it's another file to create, maintain, and reference.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/employee.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PositiveSmallIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Serializer:&lt;/strong&gt; &lt;a href="https://www.django-rest-framework.org/api-guide/serializers/"&gt;Serializers&lt;/a&gt; are going to be your definition for what a model will return. You have the ability to include/exclude fields from responses, define nested representations (show the entire &lt;code&gt;User&lt;/code&gt; model, instead of just a &lt;code&gt;user_id&lt;/code&gt; ), and create method fields that will return some value every time your object is being returned. Essentially, splitting a Laravel model returns a Django model + serializer. A serializer is technically an offering of DRF, but if you're building an API, you'll have this.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/serializers/employee.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Employee&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Employee&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Views/viewsets:&lt;/strong&gt;  &lt;a href="https://docs.djangoproject.com/en/3.2/topics/http/views/"&gt;Django views&lt;/a&gt; are Laravel's controllers. Here, you can define individual functions (similar to single-action controllers in Laravel), or entire viewsets which hold related methods (controllers). The use of "views" as terminology for a controller was one of the hardest things to wrap my head around. Below is a view &lt;a href="https://www.django-rest-framework.org/api-guide/views/"&gt;built using DRF&lt;/a&gt;, which differs slightly from a generic Django view.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/views/employee.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generics&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Employee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app.serializers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmployeeSerializer&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmployeeList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListCreateAPIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Employee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EmployeeSerializer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templates:&lt;/strong&gt; The &lt;a href="https://docs.djangoproject.com/en/3.2/topics/templates/"&gt;template layer&lt;/a&gt; is Django's presentational "view". This is where data from your views will be rendered to the user and is analogous to Laravel's views.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The winner
&lt;/h2&gt;

&lt;p&gt;There are other design patterns and features that I didn't dive into here, such as queues and signals/listeners. The usage of both is very different between Laravel and Django, where Django feels more primitive, but that flexibility is what makes it a great choice for many applications. Some may feel that Laravel is too opinionated or enforces too many rules, but that's what's great about it – it's hard to break out of the design pattern, which makes code more consistent and makes it easier to onboard new developers onto a project.&lt;/p&gt;

&lt;p&gt;Personally, I reach for Laravel's ideas in every project, regardless of what language I'm using. The structure/flow is easy to follow and the documentation is fantastic. When I was using Laravel professionally, I enjoyed it &lt;em&gt;a lot&lt;/em&gt;. Being able to use queues, caching, events, and notifications right out of the box made it easy to develop complex features without thinking about what other tools I'd have to pull in. There's a little bit more work involved with Django, but you also get to define your own starting point and make decisions how you see fit. In terms of performance, &lt;a href="https://fullscale.io/blog/django-vs-laravel-performance-comparison-of-web-application-frameworks/"&gt;Django is probably a better choice&lt;/a&gt;, but everything comes down to what you need in an application.&lt;/p&gt;

&lt;h2&gt;
  
  
  tl;dr
&lt;/h2&gt;

&lt;p&gt;Laravel adheres more closely to traditional MVC – models define business logic and attributes, controllers are used for endpoints and data manipulation, and views provide the presentation layer. &lt;/p&gt;

&lt;p&gt;Django increases complexity a bit by using different terminology – a traditional "model" is essentially a model + a serializer, views/viewsets are traditional controllers, and templates are traditional "views" that provide presentation.&lt;/p&gt;

</description>
      <category>django</category>
      <category>laravel</category>
      <category>python</category>
      <category>php</category>
    </item>
  </channel>
</rss>
