<?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: Awal Ariansyah</title>
    <description>The latest articles on DEV Community by Awal Ariansyah (@awalariansyah).</description>
    <link>https://dev.to/awalariansyah</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%2F500659%2F105d842d-5816-4560-8e95-7f13e3393148.jpeg</url>
      <title>DEV Community: Awal Ariansyah</title>
      <link>https://dev.to/awalariansyah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/awalariansyah"/>
    <language>en</language>
    <item>
      <title>OpenCV.js Without the Memory Leaks: Chainable Image Processing for Every JavaScript Runtime</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Thu, 14 May 2026 15:23:26 +0000</pubDate>
      <link>https://dev.to/awalariansyah/opencvjs-without-the-memory-leaks-chainable-image-processing-for-every-javascript-runtime-2e43</link>
      <guid>https://dev.to/awalariansyah/opencvjs-without-the-memory-leaks-chainable-image-processing-for-every-javascript-runtime-2e43</guid>
      <description>&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%2Fopolbef5mgheneqe176r.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%2Fopolbef5mgheneqe176r.png" alt="ppu-ocv cover" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have ever used OpenCV.js in production, you already know the bug. A user uploads ten images in a row. The third one throws inside your blur step. Your &lt;code&gt;mat.delete()&lt;/code&gt; line at the bottom of the function never runs. The WASM heap creeps up. By image six the tab is sluggish. By image nine it crashes. You add a try/finally. You forget one Mat in a helper function. The bug comes back.&lt;/p&gt;

&lt;p&gt;OpenCV.js is the most capable image-processing toolkit in the browser. It is also a manual-memory C++ port wearing a thin JavaScript jacket. Every operation allocates one or more &lt;code&gt;Mat&lt;/code&gt; objects on the WASM heap, and every &lt;code&gt;Mat&lt;/code&gt; is yours to free. Miss one and you leak. Miss enough and you crash.&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://www.npmjs.com/package/ppu-ocv" rel="noopener noreferrer"&gt;&lt;code&gt;ppu-ocv&lt;/code&gt;&lt;/a&gt;, an open-source TypeScript wrapper that takes the memory tax off your plate, gives you a chainable pipeline, and ships four entry points so you can opt out of OpenCV entirely when your runtime cannot afford the 8 MB WASM blob. It powers the preprocessing path inside &lt;a href="https://www.npmjs.com/package/ppu-paddle-ocr" rel="noopener noreferrer"&gt;&lt;code&gt;ppu-paddle-ocr&lt;/code&gt;&lt;/a&gt; and a handful of receipt and document pipelines in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OpenCV.js memory tax
&lt;/h2&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%2F80q0ls3adnp5uamdjyws.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%2F80q0ls3adnp5uamdjyws.png" alt="Memory tax comparison" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a four-step pipeline (grayscale, blur, threshold) in raw OpenCV.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&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;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COLOR_RGBA2GRAY&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;blurred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blurred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Mat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blurred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;THRESH_BINARY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// you remember every one of these. always.&lt;/span&gt;
&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;blurred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four operations, four Mat objects, four &lt;code&gt;.delete()&lt;/code&gt; calls. Add a &lt;code&gt;try/catch&lt;/code&gt; because the user might upload a corrupt JPEG and &lt;code&gt;cv.threshold&lt;/code&gt; will throw. Now wrap every one of those &lt;code&gt;delete()&lt;/code&gt; calls in &lt;code&gt;finally&lt;/code&gt;. Add another operation. Realize you forgot to delete the new Mat. Wonder why memory grows in production but not in your test.&lt;/p&gt;

&lt;p&gt;Same pipeline in &lt;code&gt;ppu-ocv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-ocv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initRuntime&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;processor&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;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;processor&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;threshold&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toCanvas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One &lt;code&gt;destroy()&lt;/code&gt; at the end. The pipeline owns every intermediate Mat and frees them when you tear it down. If an operation throws midway, the next &lt;code&gt;destroy()&lt;/code&gt; still walks the same list. Adding &lt;code&gt;.dilate()&lt;/code&gt; next month does not introduce a new Mat for you to track.&lt;/p&gt;

&lt;p&gt;The chainable API is the visible win. The memory contract is the load-bearing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type safety and operation order
&lt;/h2&gt;

&lt;p&gt;Every operation is typed. Options have inferred shapes. The chain compiles only if your call sites match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// no options&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// tuple is required&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;THRESH_BINARY&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// OpenCV enum reused&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canny&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;high&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operation order matters with OpenCV. &lt;code&gt;cv.threshold&lt;/code&gt; expects single-channel input. &lt;code&gt;canny&lt;/code&gt; expects a smoothed gray image. The library does not reorder your steps for you, but the operations table in the README documents which step expects what, and the types push you toward correct compositions instead of letting you stack &lt;code&gt;dilate&lt;/code&gt; on a color image and get garbage out.&lt;/p&gt;

&lt;p&gt;For custom operations, the &lt;code&gt;registry.register(...)&lt;/code&gt; hook lets you add a pipeline step from your app code without forking the library. The new operation gets the same Mat-lifecycle treatment as the built-ins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four entry points: OpenCV is opt-in
&lt;/h2&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%2Fnpxfqd8043spieyd0r0r.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%2Fnpxfqd8043spieyd0r0r.png" alt="Four entry points" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenCV.js is roughly 8 MB of WebAssembly. That is fine for a server, painful for a tab, and impossible for a Manifest V3 browser extension service worker, which caps script size and forbids &lt;code&gt;eval&lt;/code&gt; paths used by some Emscripten builds. Plenty of real image jobs do not need OpenCV at all: crop a rectangle, resize to 360×640, threshold a binary image, draw a bounding box, save the result as a PNG. Canvas APIs handle that natively.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ppu-ocv&lt;/code&gt; exposes four entry points so you load OpenCV only when the workload needs it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Import path&lt;/th&gt;
&lt;th&gt;OpenCV&lt;/th&gt;
&lt;th&gt;Canvas backend&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ppu-ocv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@napi-rs/canvas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full pipeline, Node / Bun&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ppu-ocv/web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HTMLCanvas&lt;/code&gt; / &lt;code&gt;OffscreenCanvas&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full pipeline, browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ppu-ocv/canvas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@napi-rs/canvas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Edge runtimes, lean Node services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ppu-ocv/canvas-web&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HTMLCanvas&lt;/code&gt; / &lt;code&gt;OffscreenCanvas&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;MV3 extensions, service workers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;/canvas&lt;/code&gt; and &lt;code&gt;/canvas-web&lt;/code&gt; entries never import or initialize OpenCV. &lt;code&gt;CanvasProcessor&lt;/code&gt; and &lt;code&gt;CanvasToolkit&lt;/code&gt; cover resize, grayscale, threshold, invert, border, rotate, region detection (connected-components flood-fill), crop, and image I/O. On binary images the bounding boxes returned by &lt;code&gt;findRegions&lt;/code&gt; match OpenCV's &lt;code&gt;findContours(RETR_EXTERNAL) + boundingRect&lt;/code&gt; within 1 pixel, and full-pipeline IoU against OpenCV measured at &lt;strong&gt;98.4%&lt;/strong&gt; across 21/21 boxes.&lt;/p&gt;

&lt;p&gt;In practice that means a browser extension that scans receipts can ship under a few hundred KB instead of dragging 8 MB of WASM into every page load. A service worker that wants to crop and threshold an image before passing it to a recognition model can run with zero OpenCV initialization.&lt;/p&gt;

&lt;p&gt;When you do need OpenCV (perspective warp, deskew, Canny edges, morphological gradient, contour analysis), swap the import path. The chainable API is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Powering the OCR stack
&lt;/h2&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%2F1naflz45crupigmm4sz7.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%2F1naflz45crupigmm4sz7.png" alt="Stack diagram" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ppu-ocv&lt;/code&gt; is the preprocessing engine underneath &lt;code&gt;ppu-paddle-ocr&lt;/code&gt;. The detection step inside the OCR library calls &lt;code&gt;ppu-ocv&lt;/code&gt; to normalize input images, resize to the model's expected dimensions, and crop detected text regions for the recognition pass. The OCR library exposes a &lt;code&gt;processing.engine&lt;/code&gt; option that flips between &lt;code&gt;"opencv"&lt;/code&gt; (uses &lt;code&gt;ImageProcessor&lt;/code&gt; from &lt;code&gt;ppu-ocv&lt;/code&gt;) and &lt;code&gt;"canvas-native"&lt;/code&gt; (uses &lt;code&gt;CanvasProcessor&lt;/code&gt; from &lt;code&gt;ppu-ocv/canvas-web&lt;/code&gt;). Same author, same testing surface, no version-drift surprises.&lt;/p&gt;

&lt;p&gt;Other workloads that ride the same pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document scanners&lt;/strong&gt;: deskew via the bundled &lt;code&gt;DeskewService&lt;/code&gt; (multi-method consensus: minAreaRect, baseline analysis, Hough transform). Then perspective warp via OpenCV.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receipt pipelines&lt;/strong&gt;: grayscale plus adaptive threshold before OCR. The OCR step gets a clean binary image instead of a noisy JPEG.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII redaction&lt;/strong&gt;: detect text regions, draw filled rectangles over them with &lt;code&gt;CanvasToolkit.drawLine&lt;/code&gt; and friends, serialize the masked canvas back to a buffer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser-extension capture tools&lt;/strong&gt;: crop the visible viewport, run a threshold to find tappable regions, return geometry to the content script. &lt;code&gt;canvas-web&lt;/code&gt; entry, zero OpenCV.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The shape is always the same: instantiate a processor, chain operations, call &lt;code&gt;toCanvas()&lt;/code&gt; or &lt;code&gt;toMat()&lt;/code&gt;, call &lt;code&gt;destroy()&lt;/code&gt;. Whether OpenCV is loaded depends only on which import path you used.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: receipt preprocessing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-ocv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./receipt.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initRuntime&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&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;processor&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;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;processor&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dilate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toCanvas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// hand cleaned canvas to ppu-paddle-ocr, or save it&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Bun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./out/cleaned.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;initRuntime()&lt;/code&gt; is the one-time cost. After that, every &lt;code&gt;new ImageProcessor(canvas)&lt;/code&gt; is cheap. In Node / Bun the canvas backend is &lt;code&gt;@napi-rs/canvas&lt;/code&gt;, which gives you a real &lt;code&gt;Canvas&lt;/code&gt; object on the server side without a headless browser.&lt;/p&gt;

&lt;p&gt;For the no-OpenCV path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CanvasToolkit&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-ocv/canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&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;toolkit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;CanvasToolkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&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;cropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toolkit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;x1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&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;binary&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;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cropped&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;thresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;127&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toCanvas&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;regions&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;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;findRegions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;foreground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minArea&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&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;Zero WASM downloaded. Zero &lt;code&gt;mat.delete()&lt;/code&gt; calls. Runs in a service worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next: React Native Canvas Skia
&lt;/h2&gt;

&lt;p&gt;React Native is the next entry point. The plan is a &lt;code&gt;ppu-ocv/native&lt;/code&gt; import that registers a custom &lt;code&gt;CanvasPlatform&lt;/code&gt; adapter backed by &lt;a href="https://shopify.github.io/react-native-skia/" rel="noopener noreferrer"&gt;&lt;code&gt;@shopify/react-native-skia&lt;/code&gt;&lt;/a&gt;. Skia exposes &lt;code&gt;SkImage&lt;/code&gt; and &lt;code&gt;SkSurface&lt;/code&gt; objects that map cleanly to the &lt;code&gt;CanvasLike&lt;/code&gt; interface &lt;code&gt;ppu-ocv&lt;/code&gt; already accepts, so the chainable API stays identical. The first cut targets the canvas-only path (no OpenCV on mobile), which is the right default for on-device receipt scanning and ID capture in a React Native shell.&lt;/p&gt;

&lt;p&gt;After Skia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A worker-pool wrapper so multi-page document jobs fan out across cores without you wiring up &lt;code&gt;worker_threads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;More built-in operations on the canvas-native path so the OpenCV/no-OpenCV feature gap keeps shrinking.&lt;/li&gt;
&lt;li&gt;Streaming pipelines for video frames, with reusable Mat pools to amortize allocation across frames.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ppu-ocv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-ocv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initRuntime&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;CanvasProcessor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepareCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&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;out&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;ImageProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toCanvas&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// remember the one delete() that replaces all the others:&lt;/span&gt;
&lt;span class="c1"&gt;// (the processor instance, not out)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/PT-Perkasa-Pilar-Utama/ppu-ocv" rel="noopener noreferrer"&gt;https://github.com/PT-Perkasa-Pilar-Utama/ppu-ocv&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/ppu-ocv" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ppu-ocv&lt;/a&gt;&lt;br&gt;
JSR: &lt;a href="https://jsr.io/@snowfluke/ppu-ocv" rel="noopener noreferrer"&gt;https://jsr.io/@snowfluke/ppu-ocv&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OCR companion piece on &lt;code&gt;ppu-paddle-ocr&lt;/code&gt; is here: &lt;a href="https://dev.to/awalariansyah/deterministic-ocr-in-javascript-paddleocr-for-node-bun-deno-and-the-browser-2bgn"&gt;Deterministic OCR in JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have ever fought OpenCV.js memory in production, give the four-step pipeline above a try on your own sample, and open an issue if it leaks. The whole point of this library is that it should not.&lt;/p&gt;

</description>
      <category>opencv</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deterministic OCR in JavaScript: PaddleOCR for Node, Bun, Deno, and the Browser</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Thu, 14 May 2026 14:49:15 +0000</pubDate>
      <link>https://dev.to/awalariansyah/deterministic-ocr-in-javascript-paddleocr-for-node-bun-deno-and-the-browser-2bgn</link>
      <guid>https://dev.to/awalariansyah/deterministic-ocr-in-javascript-paddleocr-for-node-bun-deno-and-the-browser-2bgn</guid>
      <description>&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%2Flwsmyqlove1kco54tv18.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%2Flwsmyqlove1kco54tv18.png" alt="ppu-paddle-ocr cover" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLMs read text from images now. So why ship a Machine Learning OCR model?&lt;/p&gt;

&lt;p&gt;Because the receipt your reconciliation job processed last night will be processed again next quarter, and the totals had better match. A GPT-class vision model can hallucinate a &lt;code&gt;5&lt;/code&gt; into an &lt;code&gt;8&lt;/code&gt;, drop a decimal, or reorder line items the second time you ask. Cloud OCR also costs money per page, leaks the document outside your network, and breaks the moment the vendor deprecates a model id.&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://www.npmjs.com/package/ppu-paddle-ocr" rel="noopener noreferrer"&gt;&lt;code&gt;ppu-paddle-ocr&lt;/code&gt;&lt;/a&gt;, an open-source TypeScript SDK for PaddleOCR. It runs the PP-OCRv5 family directly on ONNX Runtime in Node.js, Bun, Deno, the browser, and browser extensions, with the same package and the same API. This post walks through what that buys you, how it compares against the official PaddleOCR JS SDK, Tesseract.js, and LLM OCR, and what is shipping next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why deterministic OCR still matters in the LLM era
&lt;/h2&gt;

&lt;p&gt;Production pipelines need three properties that LLM OCR fights against:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility.&lt;/strong&gt; Run the same image through the same code on Monday and Friday and get the same string. PP-OCRv5 detection plus recognition is a pair of fixed convolutional and transformer graphs. The output of &lt;code&gt;recognize("./receipt.jpg")&lt;/code&gt; does not drift between calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditability.&lt;/strong&gt; When a downstream system extracts &lt;code&gt;$42.50&lt;/code&gt; from a receipt, a finance team can point at the model version, the input image, and the bounding box that produced that string. LLMs give you a paragraph of free-form text and no box geometry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency and cost.&lt;/strong&gt; A single receipt takes roughly 190 ms on an M1 with no GPU and zero network calls. The equivalent vision-LLM round trip is two orders of magnitude slower and costs real money per thousand pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LLM OCR is great for one-off semantic extraction (give me the vendor name, summarize this contract). It is the wrong tool for "ingest a million invoices a month and never disagree with yourself."&lt;/p&gt;

&lt;h2&gt;
  
  
  How the JavaScript OCR landscape compares
&lt;/h2&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%2Fhszem4gqsy5xdmlntclt.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%2Fhszem4gqsy5xdmlntclt.png" alt="OCR comparison" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A quick tour of the alternatives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The official PaddleOCR JS SDK&lt;/strong&gt; (&lt;a href="https://www.npmjs.com/package/@paddlejs-models/ocr" rel="noopener noreferrer"&gt;&lt;code&gt;@paddlejs-models/ocr&lt;/code&gt;&lt;/a&gt;) runs only in the browser, uses an older PP-OCRv4 graph through paddlejs, and was last touched years ago. You cannot drop it into a Node service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tesseract.js&lt;/strong&gt; ships LSTM-based models from a 20-year-old engine. Accuracy on receipts and modern fonts trails PP-OCRv5 by 5 to 15 character points, and there is no per-line batching, no WebGPU, and no built-in support for non-Latin scripts beyond pre-baked language packs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision LLMs&lt;/strong&gt; (GPT-4 class, Gemini, Claude with vision) are accurate but stochastic, expensive, and require your image to leave the device. The same image submitted twice can produce different field orderings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ppu-paddle-ocr&lt;/code&gt;&lt;/strong&gt; runs the current PP-OCRv5 graphs through ONNX Runtime, hits 99.22% character accuracy on the receipt benchmark, ships with a single production dependency (&lt;code&gt;ppu-ocv&lt;/code&gt;), and works in every JavaScript host you are likely to target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official SDK and Tesseract.js are not bad pieces of software. They just stop where modern JavaScript starts: server runtimes, edge workers, mobile shells, browser extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  One package, every runtime
&lt;/h2&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%2Fgmcxtdxlprs0q0n56j4h.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%2Fgmcxtdxlprs0q0n56j4h.png" alt="Runtime support" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;PaddleOcrService&lt;/code&gt; class works in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Install&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm install ppu-paddle-ocr onnxruntime-node&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bun add ppu-paddle-ocr onnxruntime-node&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deno&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deno add jsr:@snowfluke/ppu-paddle-ocr&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm install ppu-paddle-ocr onnxruntime-web&lt;/code&gt; (import &lt;code&gt;/web&lt;/code&gt; subpath)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser extension&lt;/td&gt;
&lt;td&gt;Bundle &lt;code&gt;ppu-paddle-ocr/web&lt;/code&gt; with your MV3 extension&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;React Native&lt;/td&gt;
&lt;td&gt;Shipping next (see roadmap below)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Server runs swap &lt;code&gt;onnxruntime-node&lt;/code&gt; for native ORT bindings. The browser entry point swaps to &lt;code&gt;onnxruntime-web&lt;/code&gt; and quietly enables WebGPU when the browser supports it. There is no per-runtime fork of the code: I publish one source tree and two entry points (&lt;code&gt;ppu-paddle-ocr&lt;/code&gt; for native, &lt;code&gt;ppu-paddle-ocr/web&lt;/code&gt; for canvas-native).&lt;/p&gt;

&lt;p&gt;A receipt-scanner browser extension I ship uses the same &lt;code&gt;recognize()&lt;/code&gt; call as the Bun-based ingestion pipeline that backs it. That alone removed a class of "works on my laptop, not in the extension" bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  One production dependency
&lt;/h2&gt;

&lt;p&gt;Look at the &lt;code&gt;dependencies&lt;/code&gt; block in &lt;code&gt;package.json&lt;/code&gt;:&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="nl"&gt;"dependencies"&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;"ppu-ocv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.1.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&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;"onnxruntime-node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.23.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"onnxruntime-web"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.23.2"&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;That is the entire runtime footprint. &lt;code&gt;ppu-ocv&lt;/code&gt; is my own chainable image-processing wrapper (OpenCV.js plus a canvas-native backend). The two ONNX Runtime packages are optional peers; you install whichever one matches the target you ship to, never both.&lt;/p&gt;

&lt;p&gt;What that buys you in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Predictable bundles.&lt;/strong&gt; The browser entry point pulls &lt;code&gt;onnxruntime-web&lt;/code&gt; and the canvas-native preprocessor. No OpenCV.js in the web bundle, no transitive Tesseract worker scripts, no polyfills you did not ask for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lockfile sanity.&lt;/strong&gt; &lt;code&gt;npm install ppu-paddle-ocr onnxruntime-node&lt;/code&gt; adds 2 top-level packages and a handful of transitive ones. Compare that to the Tesseract.js install which pulls in WASM core, language data loaders, and a worker bootstrap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No vendor SDK lock-in.&lt;/strong&gt; The whole library talks to a documented &lt;code&gt;InferenceSession&lt;/code&gt; interface. If a future ONNX Runtime build adds a faster execution provider, you upgrade the peer and keep your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable supply chain.&lt;/strong&gt; One prod dep means one transitive tree to review when your security team asks. The smaller that tree, the fewer surprises in a &lt;code&gt;npm audit&lt;/code&gt; report at 4pm on a Friday.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the API looks like
&lt;/h2&gt;

&lt;p&gt;The whole surface is three calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaddleOcrService&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-paddle-ocr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ocr&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;PaddleOcrService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recognize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./receipt.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;initialize()&lt;/code&gt; downloads PP-OCRv5 mobile English by default and caches it under &lt;code&gt;~/.cache/ppu-paddle-ocr&lt;/code&gt;. &lt;code&gt;recognize()&lt;/code&gt; accepts a file path, URL, &lt;code&gt;ArrayBuffer&lt;/code&gt;, &lt;code&gt;HTMLCanvasElement&lt;/code&gt;, or &lt;code&gt;OffscreenCanvas&lt;/code&gt;. &lt;code&gt;destroy()&lt;/code&gt; releases the ONNX sessions.&lt;/p&gt;

&lt;p&gt;For browsers, swap one import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaddleOcrService&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-paddle-ocr/web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ocr&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;PaddleOcrService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recognize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The four-stage pipeline
&lt;/h2&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%2Fsdcjnrjvq7nc2pdws5vf.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%2Fsdcjnrjvq7nc2pdws5vf.png" alt="Pipeline" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each &lt;code&gt;recognize()&lt;/code&gt; call walks four stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decode and normalize&lt;/strong&gt; the input image through either OpenCV.js (&lt;code&gt;ppu-ocv&lt;/code&gt;) or canvas-native preprocessing. Browsers default to canvas-native to keep bundles lean; servers default to OpenCV for tighter bounding boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run text detection&lt;/strong&gt; with &lt;code&gt;PP-OCRv5_mobile_det_infer.ort&lt;/code&gt;. Output: a set of quadrilateral boxes around every text region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose a recognition strategy.&lt;/strong&gt; &lt;code&gt;per-box&lt;/code&gt; runs one inference per region. &lt;code&gt;per-line&lt;/code&gt; (the default) merges regions on the same line into one strip. &lt;code&gt;cross-line&lt;/code&gt; bin-packs strips across lines into uniform batches, which is the fastest option when you have a dense document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode characters&lt;/strong&gt; with &lt;code&gt;en_PP-OCRv5_mobile_rec_infer.ort&lt;/code&gt; against the language dictionary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The strategy knob exists because reducing inference calls dominates wall-clock time more than any micro-optimization. On the Apple M1 benchmark in the README, &lt;code&gt;per-line&lt;/code&gt; lands at 188 ms per receipt with 99.22% character accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paddle model ecosystem you get for free
&lt;/h2&gt;

&lt;p&gt;PP-OCRv5 is not one model. It is a family, and every member has an ONNX export.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mobile vs server.&lt;/strong&gt; Mobile models fit in a few megabytes and run on a CPU. Server models trade size for two extra accuracy points on dense or low-quality documents. Swap the URL in your config; the rest of the code is unchanged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;40+ languages across five script systems.&lt;/strong&gt; Latin (English, French, German, Italian, Spanish, Portuguese, and 40+ others), Cyrillic (Russian, Ukrainian, Bulgarian, Kazakh, Serbian), Arabic (Arabic, Persian, Urdu, Kurdish), Indic (Hindi, Tamil, Telugu), East Asian (Korean, Japanese), and Thai. Each ships as a separate recognition model plus dictionary file. Pre-converted ONNX builds live in &lt;a href="https://github.com/PT-Perkasa-Pilar-Utama/ppu-paddle-ocr-models" rel="noopener noreferrer"&gt;&lt;code&gt;ppu-paddle-ocr-models&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INT8 quantization.&lt;/strong&gt; The recognition transformer's MatMul ops quantize to INT8 with no measured accuracy loss (99.22% before, 99.22% after) and a 20 to 50 percent speedup on x86-64 CPUs with VNNI and on WebAssembly. The repo ships a one-line Python script that does the conversion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PP-DocLayout, PP-Structure, PP-FormulaNet.&lt;/strong&gt; Layout, table, and formula models from the same Paddle family export the same way. The library loads any ONNX model whose I/O contract matches.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PaddlePaddle team keeps shipping new versions. Because the runtime is plain ONNX, picking up the next bump is a URL change, not a library upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to Thai (or Russian, or Arabic) in five lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://media.githubusercontent.com/media/PT-Perkasa-Pilar-Utama/ppu-paddle-ocr-models/refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DICT_BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://raw.githubusercontent.com/PT-Perkasa-Pilar-Utama/ppu-paddle-ocr-models/refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ocr&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;PaddleOcrService&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/detection/PP-OCRv5_mobile_det_infer.onnx`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recognition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/recognition/multi/thai/v5/th_PP-OCRv5_mobile_rec_infer.onnx`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;charactersDictionary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;DICT_BASE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/recognition/multi/thai/v5/ppocrv5_th_dict.txt`&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;Detection is script-agnostic. Only the recognition head and dictionary change between languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebGPU when you can get it, WASM when you can't
&lt;/h2&gt;

&lt;p&gt;In Chrome and Edge on Windows, Linux, and macOS, ORT-Web routes inference through WebGPU on its own. Numbers from the library's demo page: 2 to 5 times faster than WebAssembly on the same hardware, no code changes. When WebGPU is unavailable or a kernel falls back, the library reuses the WASM path without restarting the session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isWebGpuAvailable&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-paddle-ocr/web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;isWebGpuAvailable&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GPU path active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browser extensions feel this difference the most. A receipt-scanner popup that returns text in 300 ms is usable; one that takes 1.5 seconds is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance numbers
&lt;/h2&gt;

&lt;p&gt;Benchmarks from the repo, Apple M1, Bun 1.3.13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;benchmark                            avg (min … max)
[per-line][opencv][noCache]          188.75 ms/iter
[cross-line][opencv][noCache]        193.43 ms/iter
[per-box][opencv][noCache]           206.60 ms/iter

[per-line][canvas-native][noCache]   200.04 ms/iter
[cross-line][canvas-native][noCache] 198.32 ms/iter
[per-box][canvas-native][noCache]    212.86 ms/iter

Accuracy on receipt.jpg (ground truth: 383 chars):
  [opencv]        per-box=97.91%  per-line=99.22%  cross-line=96.34%
  [canvas-native] per-box=97.65%  per-line=98.43%  cross-line=97.65%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the same benchmark on your own hardware with &lt;code&gt;bun task bench&lt;/code&gt;. I also publish a side-by-side comparison against the official SDK at &lt;a href="https://snowfluke.github.io/paddle-ocr-comparison/" rel="noopener noreferrer"&gt;paddle-ocr-comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next: React Native and beyond
&lt;/h2&gt;

&lt;p&gt;I'm working on a React Native entry point. ONNX Runtime ships a React Native binding (&lt;code&gt;onnxruntime-react-native&lt;/code&gt;), so the path is the same approach used for the web build: route the canvas and tensor adapters through a new entry point and reuse the shared pipeline. The target is feature parity with the web build, including WebGPU on Android where the driver supports it.&lt;/p&gt;

&lt;p&gt;After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A worker-pool helper for Node so multi-page PDFs fan out across cores without you wiring up &lt;code&gt;worker_threads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Built-in support for the PP-Structure family for table extraction.&lt;/li&gt;
&lt;li&gt;Streaming results as detection completes, so the first lines render while later regions are still recognizing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ppu-paddle-ocr onnxruntime-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PaddleOcrService&lt;/span&gt; &lt;span class="p"&gt;}&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;ppu-paddle-ocr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ocr&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;PaddleOcrService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recognize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./your-image.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/PT-Perkasa-Pilar-Utama/ppu-paddle-ocr" rel="noopener noreferrer"&gt;https://github.com/PT-Perkasa-Pilar-Utama/ppu-paddle-ocr&lt;/a&gt;&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/ppu-paddle-ocr" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/ppu-paddle-ocr&lt;/a&gt;&lt;br&gt;
Slack: &lt;a href="https://join.slack.com/t/ppupaddleocrcommunity/shared_invite/zt-3uzp1uuma-lrkEq8OYBYhGdUtzRoVmUg" rel="noopener noreferrer"&gt;PPU PaddleOCR community&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you ship OCR in JavaScript today, give it a run on one of your own samples and open an issue with the result. The roadmap moves on what users actually hit, not what looks good on paper.&lt;/p&gt;

</description>
      <category>ocr</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Create a emoji-picker in dmenu</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Mon, 09 Jan 2023 13:25:45 +0000</pubDate>
      <link>https://dev.to/awalariansyah/create-a-emoji-picker-in-dmenu-h99</link>
      <guid>https://dev.to/awalariansyah/create-a-emoji-picker-in-dmenu-h99</guid>
      <description>&lt;p&gt;One of the nice things I have in Windows is that when I pressed &lt;code&gt;windows&lt;/code&gt; + &lt;code&gt;.&lt;/code&gt;, the emoji picker shows up. When I moved to linux distros as a daily driver, I kinda missed that features.&lt;/p&gt;

&lt;p&gt;You can use others packages or use IBus, but I would like some cleaner approach. This approach is presented by Luke Smith, check out his cool youtube: &lt;a href="https://www.youtube.com/watch?v=UCEXY46t3OA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=UCEXY46t3OA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm assuming you're already installed dmenu on your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get all the emoji
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/dmenu
&lt;span class="nb"&gt;cd &lt;/span&gt;dmenu

curl https://gist.githubusercontent.com/Caellian/947e9b7b0f9d42278b9308765dc4ec08/raw/66a8b264732b4da40970cb3c57df02118e47cb2b/update.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We make a directory and then use this nice script by &lt;a href="https://gist.github.com/Caellian/947e9b7b0f9d42278b9308765dc4ec08" rel="noopener noreferrer"&gt;Caellian&lt;/a&gt; to collect all available emojis and format it.&lt;/p&gt;

&lt;p&gt;You'll see a file named "emoji_list". That's the file we're going to insert into dmenu.&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%2Fvis7gq8yfrfjokgg9hpu.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%2Fvis7gq8yfrfjokgg9hpu.png" alt=" " width="732" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch dmenuemoji.sh
chmod +x dmenuemoji.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We created a file &lt;code&gt;dmenuemoji.sh&lt;/code&gt;, open it with your favorite editor.&lt;/p&gt;

&lt;p&gt;Paste the script by Luke Smith below.&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;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt; emoji_list | dmenu &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 20 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; | xclip &lt;span class="nt"&gt;-selection&lt;/span&gt; clipboard

pgrep &lt;span class="nt"&gt;-x&lt;/span&gt; dunst &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; notify-send &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xclip &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-selection&lt;/span&gt; clipboard&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; Copied!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to bind it with a keyboard shortcut.&lt;/p&gt;

&lt;p&gt;You can edit the script, for example your preferred dmenu layout or your clipboard app. Here's my final result:&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%2F5djsugvm8bbk7rj10vki.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%2F5djsugvm8bbk7rj10vki.png" alt=" " width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My final script:&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;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"#"&lt;/span&gt; emoji_list | dmenu &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 15 | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; | xclip &lt;span class="nt"&gt;-selection&lt;/span&gt; clipboard&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xclip &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-selection&lt;/span&gt; clipboard&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
&lt;/span&gt;notify-send &lt;span class="s2"&gt;"Emoji copied"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;xclip &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-selection&lt;/span&gt; clipboard&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; successfully copied!"&lt;/span&gt; &lt;span class="nt"&gt;--icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dialog-information&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>dmenu</category>
      <category>xfce</category>
      <category>emoji</category>
    </item>
    <item>
      <title>Setup PHP Laravel (Postgresql &amp; mysql) Development Environment on Ubuntu 22.04 Jammy</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Sun, 11 Dec 2022 14:42:09 +0000</pubDate>
      <link>https://dev.to/awalariansyah/setup-php-laravel-postgresql-mysql-development-environment-on-ubuntu-2204-jammy-2pie</link>
      <guid>https://dev.to/awalariansyah/setup-php-laravel-postgresql-mysql-development-environment-on-ubuntu-2204-jammy-2pie</guid>
      <description>&lt;p&gt;I'm not a big fan of PHP and mainly worked with Node.js project, this time I get a project using a legacy PHP/Laravel code bases and It's really frustrating for me to just run the code because I'm not getting used to the ecosystem.&lt;/p&gt;

&lt;p&gt;In case you are running the same issues or maybe me, I decided to wrote it here.&lt;/p&gt;

&lt;h2&gt;
  
  
  OS Information
&lt;/h2&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%2Fy76veyvqq2uo095ljc4m.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%2Fy76veyvqq2uo095ljc4m.png" alt=" " width="735" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing PHP
&lt;/h2&gt;

&lt;p&gt;Be sure which PHP version you want to install, because compatibility matter. A lot. Seriously.&lt;/p&gt;

&lt;p&gt;The default APT repository is PHP 8. In my case, I need to install &lt;code&gt;PHP 7.4&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; apache2 php7.4 php7.4-common php7.4-cli php7.4-xml php7.4-mbstring php7.4-gd php7.4-pgsql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing composer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer | &lt;span class="nb"&gt;sudo &lt;/span&gt;php &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--install-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin &lt;span class="nt"&gt;--filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Laravel global installer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer global require laravel/installer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing mysql
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mysql-server php7.4-mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing postgresql
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql pgadmin4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup postgresql &amp;amp; pgadmin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/pgadmin4/bin/setup-web.sh

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres

psql

ALTER USER postgres WITH PASSWORD &lt;span class="s1"&gt;'yourpostgrespassword'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="se"&gt;\q&lt;/span&gt;

&lt;span class="nb"&gt;logout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The default postgresql information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname: localhost
Port: 5432
Username: postgres
Password: yourpostgrespassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can access it via Pgadmin desktop app or the web version at &lt;a href="http://127.0.0.1/pgadmin4/browser/" rel="noopener noreferrer"&gt;http://127.0.0.1/pgadmin4/browser/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run existing laravel project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the project to local&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd&lt;/code&gt; into it&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;composer install&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fix every composer's lockfiles issues (usually missing php extension) (if there's any).
Example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php7.4-gd
&lt;span class="nb"&gt;sudo &lt;/span&gt;service apache2 restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;composer install&lt;/code&gt; again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt;
Make sure you have already installed python. In my case I need to install &lt;code&gt;python2&lt;/code&gt; and set the npm global variable.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python2
npm config &lt;span class="nb"&gt;set &lt;/span&gt;python /usr/bin/python2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm run dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;cp .env.example .env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Edit the &lt;code&gt;.env&lt;/code&gt; file configuration (especially in DB, make sure you created an empty database first)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;php artisan key:generate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;php artisan migrate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;php artisan serve&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hope it helps~&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>How to recover lost files in Linux using Foremost</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Thu, 01 Dec 2022 08:41:20 +0000</pubDate>
      <link>https://dev.to/awalariansyah/how-to-recover-lost-files-in-linux-using-foremost-2n4p</link>
      <guid>https://dev.to/awalariansyah/how-to-recover-lost-files-in-linux-using-foremost-2n4p</guid>
      <description>&lt;p&gt;A lot of people don't know that Foremost is a very good command line program for recovering lost files. It's actually in many distros by default and it's very easy to use, as you'll see in the article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;When a file is deleted, the data is not immediately removed from the storage device. The operating system simply removes the reference to the file from the file system table. The space that was occupied by the file is now marked as free space and can be overwritten by new data.&lt;/p&gt;

&lt;p&gt;If you act quickly, you may be able to recover lost files using Foremost. Foremost is a data recovery program that uses headers and footers to identify files. It can search a storage device for specific file types and recover them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Foremost?
&lt;/h1&gt;

&lt;p&gt;Foremost is a data recovery program that can be used to recover lost files in Linux. The program works by looking for patterns in the data that are characteristic of specific file types, and then extracting the data from the file. Foremost can be used to recover a wide variety of file types, including images, documents, and even video files. &lt;br&gt;
How does Foremost work?&lt;/p&gt;

&lt;p&gt;Foremost works by looking for patterns in the data that are characteristic of specific file types. Once a file type has been identified, Foremost will then attempt to extract the data from the file. In order to do this, Foremost uses a number of different techniques, including looking for headers and footers, and using heuristics to guess the structure of the file.&lt;/p&gt;
&lt;h1&gt;
  
  
  How to install Foremost?
&lt;/h1&gt;

&lt;p&gt;In order to use Foremost to recover lost files, you will need to have a Linux system with the Foremost package installed. The Foremost package is available through many different package managers, so consult your package manager's documentation for instructions on how to install it. Once you have Foremost installed, you can begin using it to recover lost files.&lt;/p&gt;

&lt;p&gt;For debian-based:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;foremost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For arch-based:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; foremost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  How to use Foremost?
&lt;/h1&gt;

&lt;p&gt;Assuming you have already installed Foremost, here are the steps to follow to recover lost files using this tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Scan your directory to recover
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fdisk &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fto7cc9nfedso4veek59z.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%2Fto7cc9nfedso4veek59z.png" alt="Target" width="658" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, mine is at &lt;code&gt;/dev/sdc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create an output directory for the recovered files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt; Run Foremost with desired options against the image file
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;foremost &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; png &lt;span class="nt"&gt;-i&lt;/span&gt; /dev/sdc &lt;span class="nt"&gt;-o&lt;/span&gt; /output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fucw2z23t5q9xlzpxayet.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%2Fucw2z23t5q9xlzpxayet.png" alt="Running" width="735" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use the "-i" option to specify the input file, which is typically a disk image or partition that you wish to search. The "-q" and "-v" is for quick scan and verbose/logging respectively.&lt;/p&gt;

&lt;p&gt;Once you have specified the input file, you can then use the "-o" option to specify the output file. This is the file that Foremost will write the recovered files to. It is important to note that Foremost will overwrite any existing files in the output file, so make sure that you do not specify an existing file as your output file.&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%2F9jd36rbwgpdp7r3y9n84.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%2F9jd36rbwgpdp7r3y9n84.png" alt="Result" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Foremost is a powerful data recovery tool that can be used to recover lost files in Linux. In this article, we have provided a step-by-step guide on how to use Foremost to recover lost files. We hope that this guide will be useful for you and help you get your lost files back.&lt;/p&gt;

</description>
      <category>recovery</category>
      <category>data</category>
      <category>linux</category>
      <category>foremost</category>
    </item>
    <item>
      <title>COBIT 5 Framework Implementation</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Mon, 21 Nov 2022 10:54:57 +0000</pubDate>
      <link>https://dev.to/awalariansyah/cobit-5-framework-implementation-438</link>
      <guid>https://dev.to/awalariansyah/cobit-5-framework-implementation-438</guid>
      <description>&lt;h2&gt;
  
  
  COBIT 5 Framework Implementation
&lt;/h2&gt;

&lt;p&gt;The COBIT 5 framework is a set of best practices for enterprise governance and management of information and technology. It provides a comprehensive and flexible approach to help organizations meet their IT governance needs in a cost-effective and efficient manner. This blog post will provide an overview of the COBIT 5 framework and its implementation. We will cover its key components, benefits, and challenges. By the end of this post, you should have a good understanding of what COBIT 5 is and how it can be used to improve your organization's IT governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is COBIT 5?
&lt;/h2&gt;

&lt;p&gt;COBIT 5 is the latest version of the COBIT framework, released in 2012. It is a comprehensive framework for the governance and management of enterprise IT. COBIT 5 provides guidance for all aspects of IT governance, from strategy to implementation. It is based on an updated version of the ISO/IEC 38500 standard, and includes input from over 150 experts from around the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the benefit using COBIT 5?
&lt;/h2&gt;

&lt;p&gt;There are many benefits to using COBIT 5, including its comprehensive and coordinated approach, its clear structure and terminology, and its focus on governance. Additionally, COBIT 5 provides a comprehensive framework for the implementation of an effective governance system. It also helps organizations to understand the relationships between different processes and how they contribute to organizational objectives. Finally, COBIT 5 is flexible enough to accommodate the unique needs of any organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to implement COBIT 5 in real case?
&lt;/h2&gt;

&lt;p&gt;COBIT 5 is a process-based approach for the governance and management of enterprise IT. It provides a comprehensive framework that covers all aspects of IT governance from strategy to implementation.&lt;/p&gt;

&lt;p&gt;The first step in implementing COBIT 5 is to develop a clear understanding of the organization's IT requirements. This includes identifying the specific business goals that the organization wants to achieve with IT, and determining the resources required to support these goals. Once the IT requirements are understood, the next step is to develop a COBIT 5 implementation plan. This plan should be designed to meet the specific needs of the organization and should be tailored to the organization's size, structure, and culture.&lt;/p&gt;

&lt;p&gt;Once the implementation plan is in place, the next step is to implement COBIT 5 within the organization. This process typically involves training employees on COBIT 5 and its processes, as well as deploying COBIT 5 tools and templates within the organization. The goal of this phase is to ensure that all employees are aware of COBIT 5 and its processes, and that they know how to use COBIT 5 tools to support organizational goals.&lt;/p&gt;

&lt;p&gt;The final step in implementing COBIT 5 is to monitor and review the progress of the implementation. This includes tracking metrics related to COBIT 5 usage, assessing compliance with organizational policies, and evaluating the overall effectiveness of COBIT 5 in meeting business goals.&lt;/p&gt;

</description>
      <category>cobit</category>
      <category>management</category>
      <category>it</category>
      <category>framework</category>
    </item>
    <item>
      <title>Job roles in a startup company</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Mon, 17 Oct 2022 23:04:22 +0000</pubDate>
      <link>https://dev.to/awalariansyah/job-roles-in-a-startup-company-3181</link>
      <guid>https://dev.to/awalariansyah/job-roles-in-a-startup-company-3181</guid>
      <description>&lt;p&gt;Continuing my task in Information and security task, I'm here to write several job roles and their policy. There are certain roles. Here are eight important ones to consider from &lt;a href="https://www.businessnewsdaily.com/15186-first-startup-hires.html" rel="noopener noreferrer"&gt;reference&lt;/a&gt; I found:&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Chief executive officer (CEO) and chief operations officer (COO)
&lt;/h1&gt;

&lt;p&gt;Two of the most essential players in your business will be the CEO and COO. The CEO is typically the big-picture person who controls the company’s direction, vision, and culture, whereas the COO primarily focuses on the day-to-day operations that keep your business running.&lt;/p&gt;

&lt;p&gt;You can hire externally for these positions, but the founders of the company usually assume these responsibilities. Tierra Wilson, co-founder and CMO of Lovely Impact, recommends starting as the CEO of your business before hiring out. If you and your co-founder(s) already plan to take on these titles and responsibilities, hire the following seven positions next.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Product manager
&lt;/h1&gt;

&lt;p&gt;The product manager will be your go-to on all things related to your products. This team member manages the product strategy, vision and development. They typically work closely with the engineering and marketing teams to create and market your products.&lt;/p&gt;

&lt;p&gt;Vince Repaci, senior coach at LOVR Atlantic, said that bringing on a product manager can be difficult for founders, as they are typically the initial default product manager and heavily invested in their own products or services.&lt;/p&gt;

&lt;p&gt;“When you [can] afford to bring on a project manager, though, it forces you to change the way you think about the project by documenting and training someone else in it,” Repaci told Business News Daily. “This move allows founders to start working on the business rather than in the business.”&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Chief technology officer (CTO)
&lt;/h1&gt;

&lt;p&gt;A team member who specializes in technology and development is crucial to your business’s success, especially for tech startups. Although you can hire freelance front-end and back-end engineers, it’s useful to have someone on your internal team take charge of this sector. As your team grows, you can split this role into two separate positions.&lt;/p&gt;

&lt;p&gt;“Having someone with the skills to decide what will work best for your business, as well as overseeing the integration and management of various systems, is key,” said Sue Andrews, business and HR consultant at KIS Finance. “They’ll need to consider everything from hardware to software and mobile technology.”&lt;/p&gt;

&lt;p&gt;Andrews said this team member can also take the lead in building your online presence. They can split that responsibility with your marketing manager as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  4. Chief marketing officer (CMO) and community manager
&lt;/h1&gt;

&lt;p&gt;This team member will focus on your customers and how they view your product or service. Andrews said that hiring an expert with excellent marketing and promotional skills is essential to make sure your vision reaches a wide audience.&lt;/p&gt;

&lt;p&gt;“Find a marketing manager that is a jack-of-all-trades,” Wilson said. “Until you can scale, they should be able to write copy, design collateral, code landing pages, run ad campaigns and handle social media marketing.”&lt;/p&gt;

&lt;p&gt;They should also interact with your customers and act as an interim community manager to maintain positive relationships between your business and consumers. This team member can work with the product manager to incorporate customer feedback into product development.&lt;/p&gt;

&lt;h1&gt;
  
  
  5. Sales manager
&lt;/h1&gt;

&lt;p&gt;This team member will focus on generating new leads and bringing in money for your company. Wilson said startups and small business owners who master sales first last longer.&lt;/p&gt;

&lt;p&gt;“Hire an amazing sales rep or manager, and then use the money they bring in to hire more people,” she said. “This is probably the hardest position to hire for, but [it is] worth the time and effort to get the right person.”&lt;/p&gt;

&lt;p&gt;Repaci said that a skilled sales manager with experience in your industry typically won’t require a lot of training to generate leads and close deals.&lt;/p&gt;

&lt;h1&gt;
  
  
  6. Chief financial officer (CFO)
&lt;/h1&gt;

&lt;p&gt;Experts recommend that startups outsource their accounting and finance roles, but if you have the capability to hire a CFO, it can be extremely helpful for your business.&lt;/p&gt;

&lt;p&gt;“It’s essential that you have someone on the team who is responsible for the money and has an eye for detail to manage all aspects of the company’s finances,” Andrews said. “In the early stages, this will range from major issues, such as securing bank lending and leasing premises, to everyday necessities, such as paying suppliers and managing the petty cash.” [Read related article: Startup Costs: How Much Cash Will You Need?]&lt;/p&gt;

&lt;h1&gt;
  
  
  7. Business development manager
&lt;/h1&gt;

&lt;p&gt;While similar to the sales manager, a business development manager finds ways to grow your business from both a marketing and sales standpoint. For example, this professional might focus on developing relationships with other businesses to increase revenue and potential for growth.&lt;/p&gt;

&lt;p&gt;A good business development manager identifies new business opportunities, both within your organization and with other companies. In doing so, they’ll consider new markets, areas where you might expand, new partnerships, ways to reach other existing markets, and ways to appeal to your target customers.&lt;/p&gt;

&lt;p&gt;For example, perhaps a competitor is offering a product or service you haven’t yet considered. Your business development manager will look for ways to not only keep up with their offerings but also set you apart from them to attract more attention to your brand.&lt;/p&gt;

&lt;h1&gt;
  
  
  8. Customer service representative
&lt;/h1&gt;

&lt;p&gt;Customer service is a critical task every business should master. Building positive relationships with your customers and clients is the cornerstone of your brand.&lt;/p&gt;

&lt;p&gt;It doesn’t matter how great your products or services are if your business isn’t effectively communicating with its customers and clients. Without a professional handling customer questions, calls or concerns, your reputation will inevitably suffer. You’ll want to fill this role as soon as possible&lt;/p&gt;

</description>
      <category>college</category>
      <category>information</category>
      <category>security</category>
      <category>task</category>
    </item>
    <item>
      <title>Policy strategies for information system threats</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Mon, 03 Oct 2022 17:14:45 +0000</pubDate>
      <link>https://dev.to/awalariansyah/policy-strategies-for-information-system-threats-2bb6</link>
      <guid>https://dev.to/awalariansyah/policy-strategies-for-information-system-threats-2bb6</guid>
      <description>&lt;p&gt;I'm back writing a college task for my Network and Information Security class. This time the task is to write some policy strategies based on all the threats I already mention in the previous post.&lt;/p&gt;

&lt;p&gt;Read it here &lt;a href="https://awala.me/blog/1196434/company-information-security-analysis-nnm" rel="noopener noreferrer"&gt;Company information security analysis&lt;br&gt;
&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I picked is a local Indonesian hosting company, namely Rumahweb. Rumahweb did a great job in making a policy. I will write what I understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  General Agreement
&lt;/h2&gt;

&lt;p&gt;Consists of several point of agreement between the company and the customer that is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subject of Agreement&lt;/li&gt;
&lt;li&gt;Validity of Agreement&lt;/li&gt;
&lt;li&gt;Service Agreement&lt;/li&gt;
&lt;li&gt;Service Policy&lt;/li&gt;
&lt;li&gt;Customer Rights and Obligations&lt;/li&gt;
&lt;li&gt;Customer Support&lt;/li&gt;
&lt;li&gt;Agreement Changes&lt;/li&gt;
&lt;li&gt;Violations and Sanctions&lt;/li&gt;
&lt;li&gt;Disclaimer&lt;/li&gt;
&lt;li&gt;Dispute resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a lot of stuff, you can read more &lt;a href="https://www.rumahweb.com/perjanjian-umum/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Domain Service Agreement
&lt;/h2&gt;

&lt;p&gt;Agreement that focused on the domain services, consists of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service Definition&lt;/li&gt;
&lt;li&gt;Customer Rights and Obligations&lt;/li&gt;
&lt;li&gt;Rumahweb's Rights and Responsibilities&lt;/li&gt;
&lt;li&gt;Deletion and Termination of Service&lt;/li&gt;
&lt;li&gt;Uniform Domain Name Dispute Resolution Policy (“UDRP”)&lt;/li&gt;
&lt;li&gt;Costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read more about it &lt;a href="https://www.rumahweb.com/perjanjian-domain/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Hosting Service Agreement
&lt;/h2&gt;

&lt;p&gt;The main agreement between Rumahweb and the customer for the main service which is a web hosting service. Overall it is similar to previous agreement with the addition of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service Guarantee&lt;/li&gt;
&lt;li&gt;Claims for Service Warranty Failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.rumahweb.com/perjanjian-web-hosting/" rel="noopener noreferrer"&gt;Read more&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy Policy
&lt;/h2&gt;

&lt;p&gt;Next we will be looking at the privacy policy. It is covering what data they collected, how will they use the data, third-party authentication, data confidentiality limits and the data security.&lt;/p&gt;

&lt;p&gt;What's written in the data security policy is that Rumahweb Indonesia tries its best to protect customers' personal/company data from parties who are not authorised to obtain such data, except to do the things mentioned in Data Use.&lt;/p&gt;

&lt;p&gt;That's so simple policy I'd say. Read the policy &lt;a href="https://www.rumahweb.com/kebijakan-privasi/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Acceptable Use Policy (AUP)
&lt;/h2&gt;

&lt;p&gt;Acceptable Usage Policy is a policy on the use of Rumahweb Indonesia facilities which are included in the services used by customers.&lt;/p&gt;

&lt;p&gt;It covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website content&lt;/li&gt;
&lt;li&gt;Hosting account usage&lt;/li&gt;
&lt;li&gt;E-mail facility usage&lt;/li&gt;
&lt;li&gt;Hosting resource usage&lt;/li&gt;
&lt;li&gt;VPS/Cloud Server service usage&lt;/li&gt;
&lt;li&gt;Titan Email/Flockmail service usage&lt;/li&gt;
&lt;li&gt;Alibaba Cloud VPS service usage&lt;/li&gt;
&lt;li&gt;Sanctions Against Violations&lt;/li&gt;
&lt;li&gt;Handling Violations/Abuses&lt;/li&gt;
&lt;li&gt;Contact Abuse&lt;/li&gt;
&lt;li&gt;Abuse Report Tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read full &lt;a href="https://www.rumahweb.com/acceptable-use-policy-aup" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Level Agreement (SLA)
&lt;/h2&gt;

&lt;p&gt;The last thing is the agreement in the level of service, it requires customer to understand the other policies first.&lt;/p&gt;

&lt;p&gt;The inside is consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internet Connectivity Uptime Guarantee&lt;/li&gt;
&lt;li&gt;Server Uptime Guarantee&lt;/li&gt;
&lt;li&gt;Data Security Guarantee&lt;/li&gt;
&lt;li&gt;Claim for Warranty Failure&lt;/li&gt;
&lt;li&gt;Approval of Premium WordPress Plugins and Themes&lt;/li&gt;
&lt;li&gt;Alibaba Cloud VPS Service Definition&lt;/li&gt;
&lt;li&gt;Alibaba Cloud VPS Service Downtime Guarantee&lt;/li&gt;
&lt;li&gt;Alibaba Cloud VPS Downtime Guarantee Failure Claim Process&lt;/li&gt;
&lt;li&gt;Disclaimer&lt;/li&gt;
&lt;li&gt;Amount of Compensation for Service Failure&lt;/li&gt;
&lt;li&gt;Tax Collection&lt;/li&gt;
&lt;li&gt;Billing and Payment&lt;/li&gt;
&lt;li&gt;Refund Policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read full story &lt;a href="https://www.rumahweb.com/service-level-agreement-sla/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Well, I know it's kind of lazy writing that just write the sub-heading of the content. At the very least you learn about policy of a hosting provider so you could prepare for it in case you want to open one. Thanks.&lt;/p&gt;

</description>
      <category>college</category>
      <category>information</category>
      <category>security</category>
      <category>task</category>
    </item>
    <item>
      <title>Company information security analysis</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Sun, 18 Sep 2022 13:10:55 +0000</pubDate>
      <link>https://dev.to/awalariansyah/company-information-security-analysis-nnm</link>
      <guid>https://dev.to/awalariansyah/company-information-security-analysis-nnm</guid>
      <description>&lt;p&gt;In my recent class of college, a Network and Information Security, I have tasked to do some analysis of a company and identify the information assets and also the threat of that company. So, I decided to write it here.&lt;/p&gt;

&lt;p&gt;Before we jump, I will explain briefly what I learned so far regarding to Network and Information Security.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is information system?
&lt;/h2&gt;

&lt;p&gt;I will rephrase the definition from Satzinger, Jackson and Burd.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Information System is a group that are linked one to another in purpose of gathering, processing and storing information that can be used as an output to solve a business problem. (Satzinger, Jackson, Burd: 2010).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is assets?
&lt;/h2&gt;

&lt;p&gt;Assets in terms of Information System is an assets that is important as the fundamental block of the system.&lt;/p&gt;

&lt;p&gt;Assets categorised into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personnel&lt;/li&gt;
&lt;li&gt;Hardware&lt;/li&gt;
&lt;li&gt;Application software&lt;/li&gt;
&lt;li&gt;System software&lt;/li&gt;
&lt;li&gt;Data&lt;/li&gt;
&lt;li&gt;Facility&lt;/li&gt;
&lt;li&gt;Support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a threats?
&lt;/h2&gt;

&lt;p&gt;Threats is an action or event in a company that can occurs in the form of harm, resulting in losses. It can be losses in money or costs, energy or effort and even opportunity, good reputation and bankruptcy.&lt;/p&gt;

&lt;p&gt;The category of threats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human resources failures&lt;/li&gt;
&lt;li&gt;Hardware failures&lt;/li&gt;
&lt;li&gt;Software failures&lt;/li&gt;
&lt;li&gt;External threats&lt;/li&gt;
&lt;li&gt;Internal threats&lt;/li&gt;
&lt;li&gt;Financial&lt;/li&gt;
&lt;li&gt;Nature&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The purpose of information security
&lt;/h2&gt;

&lt;p&gt;A value of information will be valued by the people if it can be trusted for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrity&lt;/li&gt;
&lt;li&gt;Confidentiality&lt;/li&gt;
&lt;li&gt;Availability&lt;/li&gt;
&lt;li&gt;Authenticity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Company analysis
&lt;/h2&gt;

&lt;p&gt;Now we are back to the task, the company I picked is a local Indonesian hosting company, namely &lt;strong&gt;Rumahweb&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rumahweb - Painless hosting solution, is a Platform as a Service (PaaS) provider that serves a web hosting as well as domain registration focusing in a low to mid-level online businesses. (&lt;a href="https://www.rumahweb.com/" rel="noopener noreferrer"&gt;https://www.rumahweb.com/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I am not trying to promote Rumahweb, but it is worth to check due to cheap cost for your development test. So, let us analyse the assets and the threats of Rumahweb.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assets analysis
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Personnel
&lt;/h4&gt;

&lt;p&gt;As a platform service, sure they had a lot of personnel. Let's just mention several of them. Customer supports, Accountant, and probably the whole IT departments such as IT support, Database Administrator, Back end Developer, Front End Developer and more.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hardware
&lt;/h4&gt;

&lt;p&gt;The hardware is a server computer with a lot of VM's running and also a datacenter to store customer's data. It can be independent server, or partnering with bigger cloud company like AWS, Alibaba, Litespeed, Google Cloud or Digital Ocean.&lt;/p&gt;

&lt;h4&gt;
  
  
  Application software
&lt;/h4&gt;

&lt;p&gt;In the client-area it will provide a web-based application of cPanel with tools and utilities.&lt;/p&gt;

&lt;h4&gt;
  
  
  System software
&lt;/h4&gt;

&lt;p&gt;Rumahweb provide a Virtual Private Server a.k.a VPS to handle system-level of business requirements.&lt;/p&gt;

&lt;h4&gt;
  
  
  Data
&lt;/h4&gt;

&lt;p&gt;They store a client's application data, the critical data of the company and other internal data such as financial data and services documentation.&lt;/p&gt;

&lt;h4&gt;
  
  
  Facility
&lt;/h4&gt;

&lt;p&gt;The company essential facility of Rumahweb is the office building. They have 2 building, a headquarter and a branch buildings located in Yogyakarta and Jakarta respectively.&lt;/p&gt;

&lt;h4&gt;
  
  
  Support
&lt;/h4&gt;

&lt;p&gt;Support or helper assets of Rumahweb is the Indonesian Law to protect the client's data and the eligibility of the company itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Threats analysis
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Human resources failure
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;No system is safe&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No matter how secure a system, a failure can happen. Especially in the human's level, a social engineering is the common threats with a fatality depends on the target and the data leaked.&lt;/p&gt;

&lt;p&gt;A hosting provider, typically can happen a failure from the non-IT staff due to Social Engineering. That does not mean an IT staff would not likely to be affected.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hardware failures
&lt;/h4&gt;

&lt;p&gt;Hardware failures can happen like the server overheat, unnoticed damage in hardware, low quality hardware. I can not think anymore example.&lt;/p&gt;

&lt;h4&gt;
  
  
  Software failures
&lt;/h4&gt;

&lt;p&gt;This category of failures is the common one among many company, it can be causes by missed User Experience implementation, outdated application, and poor user-interface.&lt;/p&gt;

&lt;h4&gt;
  
  
  External threats
&lt;/h4&gt;

&lt;p&gt;In a hosting provider scenario, the bad actor is going to target the client from the hosting provider because the client is the first-layer of the attack. &lt;/p&gt;

&lt;p&gt;The threats can be a Remote Code Execution due to poor web application deployed to the hosting, a phising, DDoS, or a malware that steal the access token's users.&lt;/p&gt;

&lt;h4&gt;
  
  
  Internal threats
&lt;/h4&gt;

&lt;p&gt;Internal threats from the perspective of the company would be a sabotage, human-error as well as non-technical problems among the staff.&lt;/p&gt;

&lt;h4&gt;
  
  
  Financial threats
&lt;/h4&gt;

&lt;p&gt;The financial threats of Rumahweb would be can not afford an infrastructure to scale the client's business either by financial miscalculation, partner regulations, and wrong financial strategy.&lt;/p&gt;

&lt;p&gt;The others would be lost to its own competitor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Nature threats
&lt;/h4&gt;

&lt;p&gt;This one is an easy analysis, if Rumahweb only relies on Regional or National data centre, the nature threats would be a Flood or a Earthquake.&lt;/p&gt;

&lt;p&gt;Because those are the most common disaster that occurs in Indonesia.&lt;/p&gt;

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

&lt;p&gt;Well, that's wrap all of it. I am sorry if you are ended up reading into this section and do not find it any useful. I wrote this as my part of college's task, so enjoy.&lt;/p&gt;

&lt;p&gt;I hope you learn something new, thank you.&lt;/p&gt;

</description>
      <category>college</category>
      <category>information</category>
      <category>security</category>
      <category>task</category>
    </item>
    <item>
      <title>A Tale of Reducing Expo/React Native Android Application</title>
      <dc:creator>Awal Ariansyah</dc:creator>
      <pubDate>Tue, 13 Apr 2021 02:55:05 +0000</pubDate>
      <link>https://dev.to/awalariansyah/a-tale-of-reducing-expo-react-native-android-application-3cgb</link>
      <guid>https://dev.to/awalariansyah/a-tale-of-reducing-expo-react-native-android-application-3cgb</guid>
      <description>&lt;p&gt;I'm going to take you to my journey on how to smoothly reduce an android application made with React Native/Expo and hopefully without getting any error. Let's begin the journey!&lt;/p&gt;

&lt;p&gt;First things first, let's talk about my development environment. I'm using &lt;code&gt;Lubuntu 20.04 focal&lt;/code&gt;, with Desktop Environment &lt;code&gt;LxQt 0.14.1&lt;/code&gt; and the details as shown in the picture below. I've successfully reduce my app for about 49% (the universal version).&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%2Fs2zjp8tecy5xo9mu8n0k.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%2Fs2zjp8tecy5xo9mu8n0k.png" alt="Alt Text" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;There are several package you need to install in order to have smooth build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install JRE, JDK and check the installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install default-jre
sudo apt-get install defalut-jdk

java -version
javac -version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdc7bnqznk1rlbt0r9xyk.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%2Fdc7bnqznk1rlbt0r9xyk.png" alt="Alt Text" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Expo Command Line
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo npm install --global expo-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install Android Command Line Tools
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo export ANDROID_HOME=/usr/lib/android-sdk
sudo wget https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip
sudo unzip commandlinetools-linux-6609375_latest.zip -d cmdline-tools
sudo mv cmdline-tools $ANDROID_HOME/
sudo export PATH=$ANDROID_HOME/cmdline-tools/tools/bin:$PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set up licenses
&lt;/h3&gt;

&lt;p&gt;Go to /usr/lib/android-sdk/ directory&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /usr/lib/android-sdk/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to accept the license before using the sdk&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo yes | sdkmanager --licenses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also we need to change the permission on &lt;code&gt;/tmp&lt;/code&gt; folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chmod -R 0777 /tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Action
&lt;/h2&gt;

&lt;p&gt;So, the first action you need to take is open up your expo react native folder in a terminal. Make sure you back up your project files first, for safety.&lt;/p&gt;

&lt;p&gt;Run the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;expo eject
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be asked with several question, and just answer it based on your project information. After you finish, there will be a new folder inside your project called android and IOs.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;android&lt;/code&gt; folder and make a new file called &lt;code&gt;local.properties&lt;/code&gt;. This file will be used for pointing the sdk that gradle need in order to build your application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd android
touch local.properties
sudo nano local.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;add the &lt;code&gt;sdk.dir&lt;/code&gt; variable like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sdk.dir = /usr/lib/android-sdk/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fd340knl5mh2t79dyxejq.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%2Fd340knl5mh2t79dyxejq.png" alt="Alt Text" width="800" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ctrl + O, Enter, Ctrl + x to save it.&lt;/p&gt;

&lt;p&gt;Next, go to the &lt;code&gt;android/app&lt;/code&gt; folder on your terminal and generate a keystore for your application so that you can publish it in Google Play Store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd android/app
sudo keytool -genkey -v -keystore yourkeyname.keystore -alias yourkeyaliasname -keyalg RSA -keysize 2048 -validity 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;yourkeyname&lt;/code&gt; and &lt;code&gt;yourkeyaliasname&lt;/code&gt;. And you will be asked for password. Make sure you remember the password.&lt;/p&gt;

&lt;p&gt;After finish, a new file will appear with the extension (.keystore). That's your key. Now head back one directory and edit the &lt;code&gt;gradle.properties&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ..
sudo nano gradle.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;add this line below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MYAPP_UPLOAD_STORE_FILE=yourkeyname.keystore
MYAPP_UPLOAD_KEY_ALIAS=yourkeyaliasname
MYAPP_UPLOAD_STORE_PASSWORD=yourkeypassword
MYAPP_UPLOAD_KEY_PASSWORD=yourkeypassword
org.gradle.jvmargs=-Xmx4096m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F06asyktnaaokemi2h3wp.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%2F06asyktnaaokemi2h3wp.png" alt="Alt Text" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;yourkeyname&lt;/code&gt;, &lt;code&gt;yourkeyaliasname&lt;/code&gt; and &lt;code&gt;yourkeypassword&lt;/code&gt; then save it.&lt;/p&gt;

&lt;p&gt;Now, go to &lt;code&gt;app&lt;/code&gt; folder and edit the &lt;code&gt;build.gradle&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd app
sudo nano build.gradle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change these line from false to true&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F1z9gu2z4ch6mad2nghz2.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%2F1z9gu2z4ch6mad2nghz2.png" alt="Alt Text" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll down into &lt;code&gt;signingConfigs&lt;/code&gt; and add this configuration below the &lt;code&gt;debug&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    release {
            if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
                storeFile file(MYAPP_UPLOAD_STORE_FILE)
                storePassword MYAPP_UPLOAD_STORE_PASSWORD
                keyAlias MYAPP_UPLOAD_KEY_ALIAS
                keyPassword MYAPP_UPLOAD_KEY_PASSWORD
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in the &lt;code&gt;buildTypes&lt;/code&gt; look in curly brackets for &lt;code&gt;release&lt;/code&gt; change this line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signingConfig signingConfigs.debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signingConfig signingConfigs.release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also add this line below the &lt;code&gt;minifyEnabled&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shrinkResources true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fiebk8h8btxroxaequlvr.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%2Fiebk8h8btxroxaequlvr.png" alt="Alt Text" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then scroll down to the last line, add a closing curly bracket (&lt;code&gt;}&lt;/code&gt;). I don't know I'm getting an error without this strange closing curly bracket.&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%2Fn8v973f93o8pl18tcavi.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%2Fn8v973f93o8pl18tcavi.png" alt="Alt Text" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing, if you also want to build an universal apk file then scroll up and search for splits, inside the &lt;code&gt;abi&lt;/code&gt; change &lt;code&gt;universalApk&lt;/code&gt; from &lt;code&gt;false&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. Save it.&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%2Fn46qhlnd8xg31wvf7xm9.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%2Fn46qhlnd8xg31wvf7xm9.png" alt="Alt Text" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If your app doesn't contain any permission, make sure you don't have any &lt;code&gt;use permission&lt;/code&gt; code inside AndroidManifest.xml. &lt;/p&gt;

&lt;p&gt;Because by default, if you're not set the permission in the package.json, expo will automatically add every permission to your app and I'm having this issue when I was trying to publish to Google play store, so make sure you check that part.&lt;/p&gt;

&lt;p&gt;Now everything is ready, time to build. Head back to &lt;code&gt;android&lt;/code&gt; folder where the &lt;code&gt;gradlew&lt;/code&gt; file exist. Log in terminal as super user, to avoid permission issues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ..
sudo su
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to build the .apk file, run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew assembleRelease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you want to build as a bundle (.aab) file run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./gradlew buildRelease
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, this take about 5-18 minutes depending on your machine and your project. The output will be in &lt;code&gt;app/build/outputs/apk/release&lt;/code&gt; for &lt;code&gt;.apk&lt;/code&gt; and &lt;code&gt;app/build/outputs/bundle/release&lt;/code&gt; for &lt;code&gt;.aab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Well, that's all I guess. Hope it help, thank you for reading and sorry for a bad English. Cheers~&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>android</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
