<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jaison D Souza</title>
    <description>The latest articles on DEV Community by Jaison D Souza (@jsndz).</description>
    <link>https://dev.to/jsndz</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3397579%2F0c39cc90-831a-4b1b-a696-df0fc3f7e48e.jpeg</url>
      <title>DEV Community: Jaison D Souza</title>
      <link>https://dev.to/jsndz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jsndz"/>
    <language>en</language>
    <item>
      <title>What Happens When You Build a Database for One Job Only?</title>
      <dc:creator>Jaison D Souza</dc:creator>
      <pubDate>Sat, 14 Nov 2026 11:23:26 +0000</pubDate>
      <link>https://dev.to/jsndz/what-happens-when-you-build-a-database-for-one-job-only-1oio</link>
      <guid>https://dev.to/jsndz/what-happens-when-you-build-a-database-for-one-job-only-1oio</guid>
      <description>&lt;p&gt;In the modern world, anything that moves generates this exact type of data: where something is(space) and when it was there(time). &lt;br&gt;
It could be a runner running around the city, a drone trying to deliver a product, or you wandering through a maze not knowing which direction you are running.&lt;/p&gt;

&lt;p&gt;When you need to store this data, the conventional choice is to reach for a general-purpose relational database like PostgreSQL or an embedded store like SQLite. And for many applications, that is a perfectly reasonable choice. General-purpose databases can certainly handle high ingestion rates when tuned properly.&lt;/p&gt;

&lt;p&gt;However, general-purpose databases are designed to support a broad range of workloads: mutable tables, random-access updates, secondary indexes, and relational joins.&lt;/p&gt;

&lt;p&gt;potDB isn't built on the premise that general-purpose databases are inherently unsuitable. Instead, it explores an alternative: what if your workload is extraordinarily predictable, and you design a lightweight storage engine optimised specifically for that narrower use case?&lt;/p&gt;

&lt;p&gt;So, let me introduce you to &lt;strong&gt;potDB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Well, before that, the initial idea for potDB wasn't this. I had an old project lying around, which was a clone of SQLite written in C. Initially, I was thinking of adding some features like crash recovery and transactions to it to make it something more than just a clone. Then I thought, since I'm planning to work on this anyway, why not build my own custom database? So, after snooping around the internet, I decided to build a simple database called &lt;strong&gt;potDB&lt;/strong&gt;. And no, "pot" isn't the pot you might be thinking of, it stands for &lt;strong&gt;Position Over Time&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 1: The Requirements
&lt;/h2&gt;

&lt;p&gt;Let's start with the what we want from this database i.e, requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional Requirements:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strict Mapping&lt;/strong&gt;: Strictly map timestamps to spatial coordinates: &lt;code&gt;timestamp -&amp;gt; (x, y)&lt;/code&gt; or &lt;code&gt;(x, y, z)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chronological Ingestion&lt;/strong&gt;: Data always arrives in real-time order. No out-of-order writes or editing past points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Flushing&lt;/strong&gt;: Flush data when the buffer is full  OR when telemetry time progresses past a configured window , supported by cooperative polling so slow streams don't get trapped in RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Range Queries&lt;/strong&gt;: Quickly filter points within a specific time window and spatial bounding box without reading the entire file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Integrity&lt;/strong&gt;: Checksum every chunk  to catch corrupted data or torn writes immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurable Durability&lt;/strong&gt;: Let the user choose between extreme speed or maximum safety.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crash Recovery&lt;/strong&gt;: If power cuts unexpectedly, already-saved data must never be corrupted, and the database should recover cleanly on restart.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Non-Functional Requirements:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Small Memory Footprint&lt;/strong&gt;: Must use a lean, predictable memory footprint so it can comfortably fit onto resource-constrained edge devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast Writes&lt;/strong&gt;: Ingestion must be fast without being bogged down by complex tree rebalancing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Third-Party Dependencies&lt;/strong&gt;: Written in clean C relying solely on standard POSIX system interfaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable&lt;/strong&gt;: The database files must follow explicit byte-level serialisation so a file written on an ARM board is readable on an x86 laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Truly Embedded&lt;/strong&gt;: Lives directly inside the client application as a library with no background database server to manage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These requirements come from the primary use cases I had in mind when envisioning potDB. For example, the database should easily fit onto a smartwatch to map the route the user is running, or run on low-power, resource-constrained edge devices.&lt;/p&gt;

&lt;p&gt;Choosing this niche means recognising that while a general-purpose database can serve most use cases, potDB is specifically designed around having a small memory footprint and working smoothly on edge hardware. Again, this is not built around the idea that it must solve every problem for every use case.&lt;/p&gt;

&lt;p&gt;To put its scale into perspective:&lt;br&gt;
We are talking about an engine with a modest, predictable memory footprint that compiles to a compact library, yet can comfortably ingest high volumes of coordinates on platforms ranging from embedded devices to laptops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 2: Designing the Database
&lt;/h2&gt;

&lt;p&gt;When designing a database, the first question that comes to mind is: which data structure should we use to store the data?&lt;/p&gt;

&lt;p&gt;There are many data structures to consider when it comes to time-series databases, such as Log-Structured Append-Only files, R-Trees, and LSM-Trees. Choosing the right data structure comes down to handling the time axis and the space axis efficiently.&lt;/p&gt;

&lt;p&gt;For the time axis, the primary architectural choices are either a &lt;strong&gt;Chunked Append-Only Log&lt;/strong&gt; (grouping sequential samples into immutable contiguous blocks) or a &lt;strong&gt;Log-Structured Merge-tree / LSM variant&lt;/strong&gt; (such as InfluxDB's TSM or Cassandra's SSTables).&lt;/p&gt;

&lt;p&gt;LSM-trees and complex merge architectures are very useful when handling out-of-order data and frequent overwrites.  But since our timestamps are monotonically increasing in chronological order, out-of-order writes are practically non-existent. Therefore, we will go with a &lt;strong&gt;Chunked Append-Only Array&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Chunked Append-Only Log
&lt;/h3&gt;

&lt;p&gt;The log is the foundation of this database.&lt;br&gt;
potDB organises all data as a strictly sequential, binary append-only stream.&lt;br&gt;
It is append-only because this database has no updates. It doesn't make sense to modify a position that has already happened in the past!&lt;/p&gt;

&lt;p&gt;Each data point is represented as a key-value pair:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt;: Timestamp&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt;: Spatial coordinates $(x, y)$ or $(x, y, z)$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;potDB supports both 2D and 3D coordinates, configured during database initialisation.&lt;/p&gt;

&lt;p&gt;This takes care of inserting data. But the next question is: should we just dump everything into one giant flat file and call it a day? &lt;br&gt;
The reading side is still a challenge. If we dump everything into a single massive file, how will we read or query it efficiently?&lt;/p&gt;

&lt;p&gt;At the very start of the database file sits a &lt;strong&gt;16-byte File Header&lt;/strong&gt;, written during database creation. It records the database magic number (&lt;code&gt;0x42445450&lt;/code&gt;, which spells &lt;code&gt;'PTDB'&lt;/code&gt; in little-endian), the version, the spatial dimension (2D or 3D), and the time unit.&lt;/p&gt;

&lt;p&gt;Following the file header, coordinates are grouped into neat, bite-sized packages called &lt;strong&gt;chunks&lt;/strong&gt; (for example, 500 points at a time).&lt;/p&gt;

&lt;p&gt;Every chunk consists of two parts: a &lt;strong&gt;Chunk Header&lt;/strong&gt; (44 bytes for 2D, 52 bytes for 3D) and the &lt;strong&gt;Payload&lt;/strong&gt; (the actual coordinates).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Why do we need each of these fields?
&lt;/h3&gt;

&lt;p&gt;The Chunk Header tells us everything we need to know about what's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Magic Number (2 Bytes)&lt;/strong&gt;:
At the very start of every chunk, we store the constant &lt;code&gt;0x4348&lt;/code&gt;(translates for &lt;code&gt;HC&lt;/code&gt; in little Endian). If these bytes do not match, the chunk is treated as corrupted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count (2 Bytes)&lt;/strong&gt;:
Tells us exactly how many coordinates are inside this chunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Window (16 Bytes)&lt;/strong&gt;:
Records the exact starting and ending time of all points in this chunk (&lt;code&gt;start_time&lt;/code&gt; and &lt;code&gt;end_time&lt;/code&gt;) for fast range pruning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounding Box (MBR) (16 Bytes for 2D / 24 Bytes for 3D)&lt;/strong&gt;:
Tracks the lowest and highest coordinates ($X, Y$ for 2D; $X, Y, Z$ for 3D) to draw a tight bounding box around all points in the chunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload Size (4 Bytes)&lt;/strong&gt;:
Tells us the exact number of bytes the coordinate data takes up on disk. If the time window or bounding box don't match what you're looking for, the database can instantly skip over the entire chunk on disk in one leap, without wasting time reading it into RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checksum (CRC-32) (4 Bytes)&lt;/strong&gt;:
Before saving the chunk, we compute an IEEE 802.3 CRC-32 checksum covering both the chunk header metadata (excluding the checksum field itself) and the coordinate payload. If the power cuts while writing or a sector on your storage goes bad, the seal won't match. potDB catches this instantly and safely ignores the bad chunk instead of feeding you garbage data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Payload&lt;/strong&gt;: 
The contiguous array of binary coordinate points.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Chapter 3: Storage and Persistence
&lt;/h2&gt;

&lt;p&gt;Based on everything we've discussed up to this point, you probably have a clear idea of &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; we are storing data.&lt;br&gt;
But &lt;em&gt;where&lt;/em&gt; are we storing it?&lt;/p&gt;

&lt;p&gt;Initially, it goes into RAM. But RAM is volatile, anything in RAM vanishes the moment the system shuts down or loses power. &lt;br&gt;
What non-volatile memory comes to our rescue? The disk (or ROM/Flash).&lt;/p&gt;

&lt;p&gt;So, problem solved, right? Not quite.&lt;br&gt;
Even though persisting everything immediately seems like the safest approach, writing every single coordinate straight to disk is terribly inefficient due to disk I/O overhead.&lt;/p&gt;

&lt;p&gt;Yet, we still need to persist the data safely.&lt;br&gt;
Like every database predecessor before us, we balance both RAM and disk. Remember the chunks we discussed earlier? Leveraging the concept of chunks, we keep an in-memory buffer matching the chunk size. We accumulate incoming coordinates in user-space RAM until the buffer is full, and only then do we flush the entire chunk sequentially. Using write(), we send the chunk to the operating system, which handles writing it to permanent storage.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Chapter 4: Crash Recovery and Durability
&lt;/h2&gt;

&lt;p&gt;What happens if, mid-flight, a drone runs out of battery and crashes? If the chunk buffer is large and the system shuts down before flushing, you lose the trajectory of the drone. I know if a drone crashes, the trajectory might be the least of your worries, but as developers, we should definitely care about protecting that data.&lt;/p&gt;

&lt;p&gt;This brings us to &lt;strong&gt;crash recovery and data durability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Traditionally, relational databases like PostgreSQL handle durability using a Write-Ahead Log (WAL). But here, there are no complex transactions or in-place table overwrites requiring a WAL. In fact, the entire database &lt;em&gt;is&lt;/em&gt; an append-only log, so why add another log on top of it? We could take a primitive approach and achieve ultra-durability by forcing every single coordinate directly to disk with &lt;code&gt;fsync()&lt;/code&gt;, like I did in my early SQLite clone. But it's called primitive for a reason: it kills performance.&lt;/p&gt;

&lt;p&gt;potDB simplifies crash recovery through its append-only chunk architecture. Since existing chunks are never modified, recovery primarily involves identifying and validating the last complete chunk.&lt;/p&gt;

&lt;p&gt;In addition to capacity-based flushing, potDB checks timestamp progression during point insertion to flush chunks when &lt;code&gt;timestamp - start_time &amp;gt;= max_time_window_ms&lt;/code&gt;. Since potDB runs as an embedded library without background threads, slow or idle streams that stop sending points can be explicitly flushed by calling &lt;code&gt;ptdb_poll&lt;/code&gt; with the current wall-clock timestamp.&lt;/p&gt;

&lt;p&gt;A crash can happen at three distinct stages:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Crash Scenarios
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scenario 1: Crash in Memory (Before Flush)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un-flushed points sitting in user-space RAM are lost on a sudden power cut or crash, but &lt;strong&gt;already-persisted chunks remain completely intact&lt;/strong&gt;. Because potDB never overwrites existing data, there are no corrupted B-tree pointers or broken node links.&lt;/li&gt;
&lt;li&gt;You can bound this loss window (e.g., set &lt;code&gt;max_time_window_ms = 1000&lt;/code&gt; for at most 1 second of data in RAM). However, two nuances are important:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event Time vs. Wall-Clock:&lt;/strong&gt; During active streaming, potDB checks the time difference between the incoming point and the first point in the chunk to decide when to flush. This is based on incoming data, not a background timer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cooperative Polling:&lt;/strong&gt; If incoming data stops, a partially filled chunk will not be flushed automatically. Since potDB has no background timer, the host application must periodically call &lt;code&gt;ptdb_poll&lt;/code&gt; to check whether the buffer needs to be flushed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scenario 2: Crash Mid-Write (Torn Chunks)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If power cuts halfway through writing a chunk, potDB detects it immediately on reboot:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Header check&lt;/strong&gt;: Checks header size and magic number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payload check&lt;/strong&gt;: Ensures &lt;code&gt;bytes_read == payload_size&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRC-32 checksum&lt;/strong&gt;: Verifies that both the chunk header metadata and payload bytes haven't suffered bit-rot or torn sectors.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Reader behavior differs by access path: sequential chunk traversal returns explicit error codes, whereas range queries  halt cleanly at the last verified chunk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;3. Scenario 3: Crash in Cache (The Durability Dial)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;write()&lt;/code&gt; sends data to the OS page cache, it may not yet be stored permanently. potDB provides three durability modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PTDB_SYNC_ALWAYS&lt;/code&gt;&lt;/strong&gt;: Calls &lt;code&gt;fsync()&lt;/code&gt; after every chunk flush. A process crash or power failure should not lose successfully synced chunks. The current chunk may be lost if the crash occurs before syncing. (~96.7K pts/sec)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PTDB_SYNC_PERIODIC&lt;/code&gt;&lt;/strong&gt;: Calls &lt;code&gt;fsync()&lt;/code&gt; every N flushes. A process crash preserves data already written to the OS, but a power failure can lose all chunks written since the last successful &lt;code&gt;fsync()&lt;/code&gt;. (~813K pts/sec at N=10)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PTDB_SYNC_NONE&lt;/code&gt;&lt;/strong&gt;: Relies on OS background writeback. A process crash generally preserves data already written to the page cache, but a power failure can lose any data not yet written to permanent storage. (~4.37M pts/sec)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Benchmarks use 500-point chunks. Actual power-loss protection depends on the OS, filesystem, and storage device.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Crash Recovery: Truncation over Traditional WAL
&lt;/h3&gt;

&lt;p&gt;Traditional relational databases maintain a Write-Ahead Log (WAL) to undo partial writes or replay committed transactions across in-place table pages. Because potDB never overwrites historical chunks in place, it does not need a traditional WAL.&lt;/p&gt;

&lt;p&gt;When a crash or system shutdown occurs mid-write, partial trailing bytes may remain on disk. potDB recovers from incomplete writes through &lt;strong&gt;file truncation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a write error occurs during &lt;code&gt;ptdb_flush()&lt;/code&gt;, the engine immediately rolls back the partial chunk using &lt;code&gt;ftruncate()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On database restart, potDB scans sequential chunks from the beginning, verifies header integrity and CRC-32 checksums, and invokes &lt;code&gt;ftruncate()&lt;/code&gt; to roll back any torn trailing bytes to the end of the last complete, valid chunk.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Chapter 5: Querying for You
&lt;/h2&gt;

&lt;p&gt;How do you query points inside a spatial bounding box across a specific window of time without scanning gigabytes of data?&lt;/p&gt;

&lt;p&gt;Because every chunk in potDB is sealed with a header containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Time Window&lt;/strong&gt;: &lt;code&gt;[start_time .. end_time]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Spatial Bounding Box (MBR)&lt;/strong&gt;: &lt;code&gt;[min_x .. max_x, min_y .. max_y]&lt;/code&gt; (plus &lt;code&gt;[min_z .. max_z]&lt;/code&gt; in 3D)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;potDB performs &lt;strong&gt;Two-Level Spatiotemporal Chunk Skipping&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Level 1 (Header Pruning)&lt;/strong&gt;: potDB sequentially scans chunk headers (reading 44 bytes for 2D or 52 bytes for 3D). If the chunk's time window or spatial MBR does not intersect your query bounds, it calls &lt;code&gt;lseek()&lt;/code&gt; to jump straight over the entire payload without reading coordinates from disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2 (Point Filtering)&lt;/strong&gt;: Only if both the time window and bounding box overlap does potDB read the payload into memory, verify its CRC-32 checksum, and stream matching points to your callback.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdgi2yn3amt2tmkbyrekf.png" alt="potDB architecture" width="800" height="347"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Chapter 6: Reliability and Concurrency Guarantees
&lt;/h2&gt;

&lt;p&gt;Even though potDB is a specialized, lightweight time-series storage engine without general SQL transactions (&lt;code&gt;BEGIN&lt;/code&gt;/&lt;code&gt;COMMIT&lt;/code&gt;), it provides specific guarantees around persistence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomicity (Chunk-Level Ingestion)&lt;/strong&gt;: potDB does not implement multi-statement ACID transactions. Instead, chunks serve as the atomic unit of persistence. Partial or torn chunk writes caused by mid-write crashes are rejected by CRC-32 integrity validation on recovery and rolled back via &lt;code&gt;ftruncate()&lt;/code&gt;, ensuring incomplete chunks are never visible to readers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;: Coordinate dimension and time units are locked in the file header at initialisation. Timestamp monotonicity is enforced at runtime during point insertion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt;: Designed for single-writer access without built-in concurrency control or multi-process locking. Range queries read completed chunks up to the last verified boundary, though concurrent uncoordinated writes without external synchronisation can cause transient read errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability&lt;/strong&gt;: Configurable per application requirements via different durability modes.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Chapter 7: Benchmarks and Performance
&lt;/h2&gt;

&lt;p&gt;All benchmarks were conducted on a specific hardware configuration. The results are empirical measurements, not universal performance guarantees.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Environment
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Specification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;AMD Ryzen 7 7840HS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;16 GB DDR5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;1 TB PCIe Gen 4 NVMe SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;CachyOS Linux, Kernel 7.2.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler&lt;/td&gt;
&lt;td&gt;GCC 16.2.1 (&lt;code&gt;-std=c99 -Wall -Wextra -D_GNU_SOURCE&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;200K–1M synthetic 2D points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Benchmark&lt;/td&gt;
&lt;td&gt;Native C (&lt;code&gt;bench/bench_ptdb.c&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Synchronization Scope: The ingestion benchmark measures the wall-clock time to insert points, serialize chunk headers, issue &lt;code&gt;write()&lt;/code&gt; calls to the OS page cache, and execute &lt;code&gt;fsync()&lt;/code&gt; barriers according to the chosen durability mode until &lt;code&gt;ptdb_close()&lt;/code&gt; flushes remaining buffers.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Architectural Properties
&lt;/h3&gt;

&lt;p&gt;These properties are determined by potDB's binary format and implementation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;2D&lt;/th&gt;
&lt;th&gt;3D&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Logical point data&lt;/td&gt;
&lt;td&gt;16 bytes&lt;/td&gt;
&lt;td&gt;20 bytes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;timestamp (8B) + coordinates (4B each)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-disk stride per point&lt;/td&gt;
&lt;td&gt;16 bytes&lt;/td&gt;
&lt;td&gt;24 bytes&lt;/td&gt;
&lt;td&gt;3D aligned to 8-byte boundary (4B padding)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chunk header&lt;/td&gt;
&lt;td&gt;44 bytes&lt;/td&gt;
&lt;td&gt;52 bytes&lt;/td&gt;
&lt;td&gt;Packed binary struct (&lt;code&gt;#pragma pack(1)&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buffer (500 points)&lt;/td&gt;
&lt;td&gt;8,000 bytes&lt;/td&gt;
&lt;td&gt;12,000 bytes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;500 * point_stride&lt;/code&gt; in user-space RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total chunk on disk (500 points)&lt;/td&gt;
&lt;td&gt;8,044 bytes&lt;/td&gt;
&lt;td&gt;12,052 bytes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Header + (500 * point_stride)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: A 3D point contains 20 bytes of actual data: an 8-byte timestamp and three 4-byte floats. However, the 64-bit system adds 4 bytes of padding, making the structure 24 bytes. potDB writes all 24 bytes to disk, allowing it to copy the structure directly without extra serialisation.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage density:&lt;/strong&gt; Binary storage uses approximately 50–75% less space than the example JSON and CSV representations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; The insertion path performs no dynamic allocations after initialisation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syscall batching:&lt;/strong&gt; A 1,000-point chunk requires two &lt;code&gt;write()&lt;/code&gt; calls (header and payload), instead of 1,000 individual writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Ingestion Throughput
&lt;/h3&gt;

&lt;p&gt;Measured ingestion throughput across different chunk capacities and durability modes, using 200,000 2D points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chunk Capacity&lt;/th&gt;
&lt;th&gt;SYNC_ALWAYS&lt;/th&gt;
&lt;th&gt;SYNC_PERIODIC (N=10)&lt;/th&gt;
&lt;th&gt;SYNC_NONE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;19.7K&lt;/td&gt;
&lt;td&gt;194.8K&lt;/td&gt;
&lt;td&gt;2.97M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;96.7K&lt;/td&gt;
&lt;td&gt;813K&lt;/td&gt;
&lt;td&gt;4.37M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;188.1K&lt;/td&gt;
&lt;td&gt;1.39M&lt;/td&gt;
&lt;td&gt;4.61M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;774.5K&lt;/td&gt;
&lt;td&gt;3.25M&lt;/td&gt;
&lt;td&gt;5.49M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;1.25M&lt;/td&gt;
&lt;td&gt;4.25M&lt;/td&gt;
&lt;td&gt;5.29M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;td&gt;3.05M&lt;/td&gt;
&lt;td&gt;4.75M&lt;/td&gt;
&lt;td&gt;5.40M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All values are points per second.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Larger chunks amortise synchronisation overhead across more points.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SYNC_ALWAYS:&lt;/strong&gt; Synchronises after every chunk, providing the strongest synchronisation frequency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SYNC_PERIODIC:&lt;/strong&gt; Synchronises every 10 flushes, balancing throughput and durability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SYNC_NONE:&lt;/strong&gt; Delegates synchronisation to the operating system's background writeback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Sequential Scan Performance
&lt;/h3&gt;

&lt;p&gt;Tested using 500,000 points stored in 1,000-point chunks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scan time&lt;/td&gt;
&lt;td&gt;29.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;17.2M points/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data bandwidth&lt;/td&gt;
&lt;td&gt;263.32 MB/sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRC-32 validation&lt;/td&gt;
&lt;td&gt;All 500 chunks verified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scan was performed against the operating system cache, with CRC-32 verification enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. In-Memory Accessor Performance
&lt;/h3&gt;

&lt;p&gt;Measured byte-level deserialisation from an already-loaded memory buffer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Accessor&lt;/th&gt;
&lt;th&gt;Time per Point&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ptdb_get_point_2d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;10.7–11.0 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ptdb_get_point_3d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;13.3–15.3 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These measurements represent in-memory decoding, not disk access latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Takeaway
&lt;/h3&gt;

&lt;p&gt;The results demonstrate how chunk capacity and durability configuration influence potDB's performance. Larger chunks significantly improve ingestion throughput, particularly when synchronisation is enabled, while sequential scanning and in-memory decoding remain efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 8:  Limitations and Trade-offs
&lt;/h2&gt;

&lt;p&gt;A specialised engine is defined as much by what it refuses to do as by what it implements. potDB makes deliberate engineering trade-offs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No Out-of-Order Ingestion&lt;/strong&gt;: All points must arrive with non-decreasing timestamps. Ingestion of late-arriving or historical out-of-order data is rejected. Applications requiring historical backfilling should consider LSM-based architectures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No In-Place Point Updates or Deletions&lt;/strong&gt;: Historical coordinates are immutable. You cannot modify or delete an individual point recorded ten minutes ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Generalised Relational Schema&lt;/strong&gt;: potDB stores only timestamps and spatial coordinates. It does not support arbitrary columns, dynamic metadata attributes, strings, or relational joins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-Writer Concurrency&lt;/strong&gt;: The engine does not provide multi-process writer coordination or file locking. Ingestion must be handled by a single dedicated writer process or thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sequential Header Scanning&lt;/strong&gt;: Without the planned sparse index, range queries must read every chunk header sequentially, making query performance proportional to the total number of chunks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These limitations are deliberate design decisions tailored for predictable, embedded spatial telemetry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 9: Future Improvements &amp;amp; Planned Features
&lt;/h2&gt;

&lt;p&gt;potDB is already fast and functional, but building a database is a never-ending rabbit hole. Here are the features and improvements I'm planning to add down the road:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binary Search on Time (Sparse Index)&lt;/strong&gt;:&lt;br&gt;
Right now, time queries check chunk headers one by one. Since time is strictly sorted, building a tiny in-memory index of chunk start times will allow us to use &lt;strong&gt;binary search&lt;/strong&gt;, jumping directly to the requested time window without sequentially scanning preceding headers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spatial Grid Index&lt;/strong&gt;:&lt;br&gt;
For bounding box queries across long periods, we still have to check every chunk's MBR. Adding a simple &lt;strong&gt;coarse spatial grid&lt;/strong&gt; (dividing space into large tiles) or a space-filling curve (like Z-order) will let us look up only the chunks touching that specific geographic area.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delta Compression (Gorilla-style)&lt;/strong&gt;:&lt;br&gt;
Coordinates and timestamps from moving objects often change gradually between consecutive samples. Using delta compression (storing differences between consecutive points instead of full raw values) could noticeably reduce on-disk file size with modest CPU overhead.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Circular Ring-Buffer &amp;amp; Auto-Retention (TTL)&lt;/strong&gt;:&lt;br&gt;
On small devices with limited flash storage (like smartwatches or micro-controllers), disk space is precious. Adding an automatic retention mode to prune chunks older than $N$ days, or rolling over a fixed-size ring-buffer will prevent disks from filling up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;First-Party Language Packages&lt;/strong&gt;:&lt;br&gt;
Packaging potDB into standard package managers (&lt;code&gt;pip install potdb&lt;/code&gt; for Python data science, &lt;code&gt;npm install potdb&lt;/code&gt; for Node/Next.js, and an official Go module) so developers can embed it into their favourite stack without manual C linking.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Designing systems is a fundamental part of software engineering, especially in the era of AI-assisted coding. Taking the time to design things from scratch helps you understand how systems truly work under the hood. &lt;/p&gt;

&lt;p&gt;I learned an enormous amount while building potDB. And I am not a seasoned database architect who knows everything. These are my thoughts, experiments, and architectural decisions. If you have any suggestions, feedback, or corrections, please feel free to drop a comment. &lt;/p&gt;

&lt;p&gt;I hope you learned something new today!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Check out the introductory site and interactive live demo: &lt;a href="https://potdb.vercel.app/" rel="noopener noreferrer"&gt;https://potdb.vercel.app/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Codebase: &lt;a href="https://github.com/jsndz/potdb" rel="noopener noreferrer"&gt;https://github.com/jsndz/potdb&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>learning</category>
      <category>c</category>
    </item>
    <item>
      <title>How HTML works? In Simple words.</title>
      <dc:creator>Jaison D Souza</dc:creator>
      <pubDate>Thu, 19 Feb 2026 18:30:52 +0000</pubDate>
      <link>https://dev.to/jsndz/how-html-works-in-simple-words-3eh1</link>
      <guid>https://dev.to/jsndz/how-html-works-in-simple-words-3eh1</guid>
      <description>&lt;p&gt;HTML is a markup language responsible for the &lt;strong&gt;structure of a web page&lt;/strong&gt;.&lt;br&gt;
The &lt;strong&gt;skeleton of a web page&lt;/strong&gt; is HTML.&lt;/p&gt;

&lt;p&gt;Instead of talking about various tags and how to use them, let us understand &lt;strong&gt;how HTML works&lt;/strong&gt;, how it renders, and how the browser displays it.&lt;/p&gt;

&lt;p&gt;When a client asks a server for a web page (this happens when you load a link), the server sends an &lt;strong&gt;HTML text file&lt;/strong&gt;.&lt;br&gt;
It is a &lt;strong&gt;plain text file&lt;/strong&gt;, nothing else.&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;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt; My Web Page &lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt; Hello World &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser cannot directly render this raw text in a meaningful way.&lt;br&gt;
So it must &lt;strong&gt;convert the text into internal structures&lt;/strong&gt; that it understands.&lt;/p&gt;

&lt;p&gt;I will briefly explain the steps involved.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tokenization and State Machine
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Tokenization
&lt;/h3&gt;

&lt;p&gt;The browser reads the HTML &lt;strong&gt;character by character&lt;/strong&gt; and converts it into &lt;strong&gt;tokens&lt;/strong&gt;.&lt;br&gt;
Tokens follow a set of structured rules defined by the HTML specification.&lt;/p&gt;

&lt;p&gt;The HTML code shown earlier would be converted into tokens like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StartTag: html&lt;/li&gt;
&lt;li&gt;StartTag: head&lt;/li&gt;
&lt;li&gt;StartTag: title&lt;/li&gt;
&lt;li&gt;Text: My Web Page&lt;/li&gt;
&lt;li&gt;EndTag: title&lt;/li&gt;
&lt;li&gt;EndTag: head&lt;/li&gt;
&lt;li&gt;StartTag: body&lt;/li&gt;
&lt;li&gt;StartTag: p&lt;/li&gt;
&lt;li&gt;Text: Hello World&lt;/li&gt;
&lt;li&gt;EndTag: p&lt;/li&gt;
&lt;li&gt;EndTag: body&lt;/li&gt;
&lt;li&gt;EndTag: html&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why is this conversion needed, and how does it happen?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This process is implemented using a &lt;strong&gt;state machine&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;The diagram shows multiple states with transitions based on characters such as &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and text content. These transitions help the parser identify opening tags, closing tags, and text nodes.&lt;/p&gt;

&lt;p&gt;The state machine follows strict rules to convert the input stream into tokens.&lt;br&gt;
Although it may look complicated, it is essentially a series of &lt;strong&gt;conditional transitions&lt;/strong&gt; (similar to if-else logic).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do we need tokens?&lt;/strong&gt;&lt;br&gt;
Tokens make it easier to convert the HTML into a DOM because each token clearly represents &lt;strong&gt;what kind of element it is&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  DOM, CSSOM, and Render Tree
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 2: Tokens → DOM
&lt;/h3&gt;

&lt;p&gt;The next step is building the &lt;strong&gt;DOM (Document Object Model)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The tokens are processed and converted into a &lt;strong&gt;tree structure&lt;/strong&gt; called the DOM.&lt;br&gt;
Each node in the tree represents an element, text node, or attribute from the HTML document.&lt;/p&gt;

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

&lt;p&gt;A hierarchical structure starting with the &lt;code&gt;html&lt;/code&gt; root node, branching into &lt;code&gt;head&lt;/code&gt; (containing &lt;code&gt;title → My Web Page&lt;/code&gt;) and &lt;code&gt;body&lt;/code&gt; (containing &lt;code&gt;p → Hello World&lt;/code&gt;).&lt;/p&gt;


&lt;h3&gt;
  
  
  Step 3: DOM + CSSOM → Render Tree
&lt;/h3&gt;

&lt;p&gt;When CSS is added, the browser parses it into another structure called the &lt;strong&gt;CSSOM (CSS Object Model)&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM contains &lt;strong&gt;structure and content&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;CSSOM contains &lt;strong&gt;styling rules&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The browser combines the DOM and CSSOM to build the &lt;strong&gt;Render Tree&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Render Tree contains &lt;strong&gt;only visible elements&lt;/strong&gt; and the styles that apply to them (for example, elements with &lt;code&gt;display: none&lt;/code&gt; are excluded).&lt;/p&gt;

&lt;p&gt;The Render Tree defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what needs to be displayed&lt;/li&gt;
&lt;li&gt;how it should be styled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note on JavaScript:&lt;/strong&gt;&lt;br&gt;
When JavaScript modifies the DOM, for example:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;div&amp;gt;Hello&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the browser may trigger &lt;strong&gt;layout recalculation (reflow)&lt;/strong&gt; and &lt;strong&gt;repainting&lt;/strong&gt;.&lt;br&gt;
However, &lt;strong&gt;the entire tree is not always re-rendered&lt;/strong&gt;—only the affected parts are updated when possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Layout and Paint
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 4: Layout Calculation (Reflow)
&lt;/h3&gt;

&lt;p&gt;In this step, the browser calculates the &lt;strong&gt;exact position and size&lt;/strong&gt; of each node in the Render Tree.&lt;/p&gt;

&lt;p&gt;For every node, the browser computes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;width&lt;/li&gt;
&lt;li&gt;height&lt;/li&gt;
&lt;li&gt;position&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process is recursive and can be expensive, especially when changes affect large portions of the page.&lt;/p&gt;

&lt;p&gt;Layout calculation produces precise instructions required to generate the final visual output.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5: Paint
&lt;/h3&gt;

&lt;p&gt;During the paint phase, the browser converts layout information into &lt;strong&gt;actual pixels&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This includes painting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text&lt;/li&gt;
&lt;li&gt;colors&lt;/li&gt;
&lt;li&gt;borders&lt;/li&gt;
&lt;li&gt;backgrounds&lt;/li&gt;
&lt;li&gt;shadows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layout → Paint → Pixels on the screen&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Optional next step (for completeness)
&lt;/h3&gt;

&lt;p&gt;Modern browsers often perform an additional step called &lt;strong&gt;Compositing&lt;/strong&gt;, where painted layers are combined and optimized (often using the GPU) before being displayed on the screen.&lt;/p&gt;




&lt;p&gt;That's all.  Thank you reading till the end. You might have a basic idea about how HTML and small part of browser works. These were my learning based on studying the working of HTML. If any mistakes are there fell free to correct me.&lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Ditch JSON, Go Binary: A Simple Message Protocol</title>
      <dc:creator>Jaison D Souza</dc:creator>
      <pubDate>Sun, 24 Aug 2025 10:17:49 +0000</pubDate>
      <link>https://dev.to/jsndz/ditch-json-go-binary-a-simple-message-protocol-e6l</link>
      <guid>https://dev.to/jsndz/ditch-json-go-binary-a-simple-message-protocol-e6l</guid>
      <description>&lt;p&gt;&lt;strong&gt;Binary Message Protocol&lt;/strong&gt; — that's what I am calling this method. Sounds cool right? Well maybe not.&lt;br&gt;&lt;br&gt;
Fancy name, but it’s really just a simple method of sending data by message framing.&lt;/p&gt;


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

&lt;p&gt;In real-time applications, &lt;strong&gt;speed and efficiency are critical&lt;/strong&gt;. Sending large JSON payloads over WebSockets can add unnecessary overhead, making communication slower and more resource-intensive.  &lt;/p&gt;

&lt;p&gt;At the same time, the server needs a clear way to identify what kind of message it's receiving. For example, whether it's a request to &lt;strong&gt;join a session&lt;/strong&gt;, an &lt;strong&gt;update&lt;/strong&gt;, or some other action.  &lt;/p&gt;

&lt;p&gt;When I was building a collaboration platform with &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Golang&lt;/strong&gt;, I ran into this exact problem: &lt;strong&gt;JSON was too heavy for real-time communication&lt;/strong&gt;, and I needed a faster, lighter protocol that still allowed the server to understand the type of each message.  &lt;/p&gt;


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

&lt;p&gt;Joining:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"join"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Updating a document:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "type": "update",&lt;br&gt;
  "payload": { "doc_id": "abc123", "delta": [1, 0, 2, 3] }&lt;br&gt;
}&lt;br&gt;
// The delta could be very large&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Looks fine — but that's a &lt;strong&gt;lot of extra characters&lt;/strong&gt; being sent on every message.&lt;/p&gt;




&lt;h2&gt;
  
  
  Solution: Binary Message Protocol
&lt;/h2&gt;

&lt;p&gt;A simple method where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;First byte = message type&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remaining bytes = payload&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it.&lt;/p&gt;

&lt;p&gt;So instead of sending the verbose JSON above, we just send something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
[2, 21, 1212, 1, 21, 2, 4, 243, 544, 4, 5, 67, 7]&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; = message type (&lt;code&gt;join&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The rest = encoded payload&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Message Type Mapping
&lt;/h2&gt;

&lt;p&gt;Here’s an example mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Byte&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;update&lt;/td&gt;
&lt;td&gt;Document update (deltas)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;join&lt;/td&gt;
&lt;td&gt;Join a document session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;leave&lt;/td&gt;
&lt;td&gt;Leave a document session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;ping&lt;/td&gt;
&lt;td&gt;Keep-alive / heartbeat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;auth&lt;/td&gt;
&lt;td&gt;Authentication / validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Encoding &amp;amp; Decoding
&lt;/h2&gt;

&lt;p&gt;This isn’t just theory — here’s how you could implement it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encoding in Go
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;go&lt;br&gt;
func (c *Client) HandleNotification(msgType uint, message string) {&lt;br&gt;
    msgBytes := []byte(message)&lt;br&gt;
    payload := make([]byte, 1+len(msgBytes))&lt;br&gt;
    payload[0] = byte(msgType)&lt;br&gt;
    copy(payload[1:], msgBytes)&lt;br&gt;
    c.Send &amp;lt;- payload&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoding in TypeScript
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;ts&lt;br&gt;
function parseMessage(message: Uint8Array): {&lt;br&gt;
  type: number;&lt;br&gt;
  payload: Uint8Array;&lt;br&gt;
} {&lt;br&gt;
  const type = message[0];&lt;br&gt;
  const payload = message.slice(1);&lt;br&gt;
  return { type, payload };&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sending as Binary over WebSocket
&lt;/h2&gt;

&lt;p&gt;In Go, you just tell the WebSocket library that you're writing &lt;strong&gt;binary messages&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;go&lt;br&gt;
func (c *Client) WritePump() {&lt;br&gt;
    for msg := range c.Send {&lt;br&gt;
        // websocket.BinaryMessage ensures data is sent as binary&lt;br&gt;
        if err := c.Conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {&lt;br&gt;
            log.Println("write error:", err)&lt;br&gt;
            return&lt;br&gt;
        }&lt;br&gt;
    }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not JSON? Why Not Protobuf?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; → Easy to use but heavy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protobuf&lt;/strong&gt; → Lightweight but complex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary Message Protocol&lt;/strong&gt; → Minimal, custom, and just enough for real-time collaboration.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This might not be advanced, and many senior engineers may find it straightforward, but I just wanted to share what I learned. Hopefully, you found it useful too.&lt;/p&gt;




&lt;h3&gt;
  
  
  Project
&lt;/h3&gt;

&lt;p&gt;🔗 &lt;a href="https://github.com/jsndz/Kairo" rel="noopener noreferrer"&gt;Kairo on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;




</description>
    </item>
    <item>
      <title>Build a GitHub App in 5 Minutes with Probot</title>
      <dc:creator>Jaison D Souza</dc:creator>
      <pubDate>Tue, 29 Jul 2025 15:28:13 +0000</pubDate>
      <link>https://dev.to/jsndz/build-a-github-app-in-5-minutes-with-probot-pc6</link>
      <guid>https://dev.to/jsndz/build-a-github-app-in-5-minutes-with-probot-pc6</guid>
      <description>&lt;h1&gt;
  
  
  Build a GitHub App in 5 Minutes with Probot
&lt;/h1&gt;

&lt;p&gt;In this article I will show you how to build your own GitHub app in 5 minutes. You can put this in your portfolio, show a friend or just do it for fun. Without any further ado, let’s begin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ANY code editor.&lt;/li&gt;
&lt;li&gt;Node.js installed.&lt;/li&gt;
&lt;li&gt;Little bit of programming knowledge. Just a little.&lt;/li&gt;
&lt;li&gt;Oh, and a GitHub account.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Setup the project
&lt;/h2&gt;

&lt;p&gt;Open a code editor. I will use VS Code. Then open a terminal.&lt;/p&gt;

&lt;p&gt;Run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-probot-app app_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Enter the name, description, and your name. Then it will ask you to choose a template.&lt;/p&gt;

&lt;p&gt;If you’re using JavaScript, select &lt;code&gt;basic-js&lt;/code&gt;; for TypeScript, choose &lt;code&gt;basic-ts&lt;/code&gt; — which is what I'll use in this example.&lt;/p&gt;

&lt;p&gt;There are also some built-in templates like &lt;code&gt;checks&lt;/code&gt;, &lt;code&gt;deploy&lt;/code&gt;, and &lt;code&gt;git-data&lt;/code&gt;, which you can explore if you'd like.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Step 2: Initialize your Smee client
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://smee.io/new" rel="noopener noreferrer"&gt;https://smee.io/new&lt;/a&gt; to generate a channel and copy the URL.&lt;/p&gt;

&lt;p&gt;Open terminal and run the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npm install --global smee-client&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then open &lt;code&gt;.env.example&lt;/code&gt; file and rename it to &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Paste the URL from Smee in front of &lt;code&gt;WEBHOOK_PROXY_URL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgif85h6b68hqjqhdnecq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgif85h6b68hqjqhdnecq.png" alt=" " width="780" height="273"&gt;&lt;/a&gt;&lt;br&gt;
You might be wondering what to paste in &lt;code&gt;APP_ID&lt;/code&gt;?&lt;br&gt;
We will do that in the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Initialize GitHub App
&lt;/h2&gt;

&lt;p&gt;Go to: &lt;a href="https://github.com/settings/apps/new" rel="noopener noreferrer"&gt;https://github.com/settings/apps/new&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out the name, description, and homepage URL of your app. If you don’t have a homepage URL, just give your GitHub page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Webhook URL&lt;/strong&gt;: Use the same &lt;code&gt;WEBHOOK_PROXY_URL&lt;/code&gt; from the previous step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook Secret&lt;/strong&gt;: Use whatever you set in your &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;: Choose permissions required for your app. If unsure, use &lt;code&gt;checks&lt;/code&gt;, &lt;code&gt;contents&lt;/code&gt;, &lt;code&gt;issues&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, and &lt;code&gt;pull requests&lt;/code&gt;. You can change them later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click on &lt;strong&gt;Create GitHub App&lt;/strong&gt;. You will be redirected.&lt;/p&gt;

&lt;p&gt;Copy the &lt;strong&gt;App ID&lt;/strong&gt; and paste it into your &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Scroll down to the &lt;strong&gt;Private Keys&lt;/strong&gt; section.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Generate a Private Key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1d3taapwere61qt88eh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1d3taapwere61qt88eh.png" alt=" " width="800" height="198"&gt;&lt;/a&gt;&lt;br&gt;
A &lt;code&gt;.pem&lt;/code&gt; file will be downloaded. Copy the file into your project folder.&lt;/p&gt;

&lt;p&gt;Rename it to &lt;code&gt;github_key.pem&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Remember to add &lt;code&gt;github_key.pem&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; files to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Paste this into the &lt;code&gt;.env&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;env&lt;br&gt;
PRIVATE_KEY_PATH="./github_key.pem"&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then in terminal, open your app folder and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
cd app_name&lt;br&gt;
npm start&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Step: Add a Simple Feature
&lt;/h2&gt;

&lt;p&gt;You have successfully created your GitHub App. You can now modify the code to respond to events like &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull_request&lt;/code&gt;, or &lt;code&gt;issues&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example that comments "thank you for raising the issue!" on every new issue:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`ts&lt;br&gt;
import { Probot } from "probot";&lt;/p&gt;

&lt;p&gt;export default (app: Probot) =&amp;gt; {&lt;br&gt;
  app.on("issues.opened", async (context) =&amp;gt; {&lt;br&gt;
    const issueComment = context.issue({&lt;br&gt;
      body: "thank you for raising the issue!",&lt;br&gt;
    });&lt;br&gt;
    await context.octokit.issues.createComment(issueComment);&lt;br&gt;
  });&lt;br&gt;
};&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s how you build your own GitHub App with Probot — quick and easy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Check It Out
&lt;/h2&gt;

&lt;p&gt;I used this same method to build &lt;strong&gt;&lt;a href="https://github.com/jsndz/codexa" rel="noopener noreferrer"&gt;Codexa&lt;/a&gt;&lt;/strong&gt; — a GitHub Copilot-like tool for reviewing pull requests.&lt;br&gt;
Feel free to check out the code and see how it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code&lt;/strong&gt;: &lt;a href="https://github.com/jsndz/blogs/tree/master/tutorial" rel="noopener noreferrer"&gt;github.com/jsndz/blogs/tree/master/tutorial&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Wanna connect?&lt;/strong&gt; Reach out on &lt;a href="https://www.linkedin.com/in/jaison-dsouza-414082263/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Also,&lt;/strong&gt; check out more of my &lt;a href="https://jsndz.vercel.app/" rel="noopener noreferrer"&gt;projects&lt;/a&gt; on &lt;a href="https://github.com/jsndz" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
