<?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: Saurabh Saha</title>
    <description>The latest articles on DEV Community by Saurabh Saha (@saurabh_saha_bf5fd7abb989).</description>
    <link>https://dev.to/saurabh_saha_bf5fd7abb989</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%2F2740408%2Fd9b5c192-3798-4be5-8d78-fd0dcfa58304.png</url>
      <title>DEV Community: Saurabh Saha</title>
      <link>https://dev.to/saurabh_saha_bf5fd7abb989</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saurabh_saha_bf5fd7abb989"/>
    <language>en</language>
    <item>
      <title>🚀 Build Dashboard Using Google Sheets API 🚀</title>
      <dc:creator>Saurabh Saha</dc:creator>
      <pubDate>Tue, 04 Feb 2025 20:07:37 +0000</pubDate>
      <link>https://dev.to/saurabh_saha_bf5fd7abb989/build-dashboard-using-google-sheets-api-2fad</link>
      <guid>https://dev.to/saurabh_saha_bf5fd7abb989/build-dashboard-using-google-sheets-api-2fad</guid>
      <description>&lt;p&gt;🎯 &lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We currently have candidate login and signup data, but we are not fully prepared to build a dedicated dashboard product to support a client. Developing a new dashboard requires coding effort, particularly integrating UI-based libraries that we are not equipped to handle. Additionally, most existing dashboard services come with additional costs, which we want to avoid for now. ❌💰&lt;/p&gt;

&lt;p&gt;As an alternative, using &lt;strong&gt;Google Sheets&lt;/strong&gt; for a semi-manual dashboard is a great idea, especially since Google offers powerful &lt;strong&gt;Apps Script&lt;/strong&gt; automation. This allows us to keep our data up-to-date without maintaining a full-fledged web-based dashboard. ✅📊&lt;/p&gt;

&lt;p&gt;🌟 &lt;strong&gt;Solution: Using Google Sheets as a Dashboard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Google provides the necessary infrastructure for handling live data updates within Google Sheets using Apps Script. By leveraging this, we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📡  Fetch candidate data from our API.&lt;/li&gt;
&lt;li&gt;📄 Store and visualize data in Google Sheets.&lt;/li&gt;
&lt;li&gt;🔄  Automate updates using triggers to refresh data periodically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🔹 &lt;strong&gt;Steps to Implement&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🆔  Get Google File and Sheet ID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each Google Sheet has a unique file ID that can be accessed from the URL. Individual sheets within the file have unique sheet IDs.&lt;/p&gt;

&lt;p&gt;🔍 &lt;strong&gt;How to Get Google File and Sheet ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From a Google Sheets URL:&lt;br&gt;
&lt;code&gt;https://docs.google.com/spreadsheets/d/FILE_ID/edit#gid=SHEET_ID&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;📂 &lt;strong&gt;File ID:&lt;/strong&gt; Found between /d/ and /edit&lt;br&gt;
📑 &lt;strong&gt;Sheet ID:&lt;/strong&gt; Found after gid= in the URL&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🖥️  &lt;strong&gt;Write an Apps Script to fetch and store API data in Google Sheets.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a basic script to fetch data from an API and update Google Sheets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fetchAPIData() {
  var url = "https://api.example.com/data";
  var response = UrlFetchApp.fetch(url);
  var json = JSON.parse(response.getContentText());

  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  sheet.clear();

  var headers = json.headers;
  sheet.appendRow(headers);

  json.rows.forEach(function(row) {
    sheet.appendRow(row);
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;⏰ Set a Trigger to refresh the data at regular intervals.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🔐 Secure the API using an App Key for controlled access.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Issues Encountered&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;🚫 &lt;strong&gt;ngrok is blocked&lt;/strong&gt; – We were unable to use ngrok for API tunneling.&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;localhost did not work&lt;/strong&gt; – Direct local API connections failed.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Hosting with our existing domain worked&lt;/strong&gt; – Using an AppRunner custom domain (pointed via GoDaddy) successfully allowed API access.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🏗️ &lt;strong&gt;API Structure&lt;/strong&gt;&lt;br&gt;
A simple API should return structured JSON data.&lt;br&gt;
The API should parse CSV headers and rows dynamically.&lt;/p&gt;

&lt;p&gt;Example JSON structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "headers": ["Name", "Email", "Signup Date"],
  "rows": [
    ["John Doe", "john@example.com", "2024-02-01"],
    ["Jane Smith", "jane@example.com", "2024-02-02"]
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔄 Architecture Flow&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;⏳  Trigger: Google Apps Script trigger initiates the data refresh.&lt;/li&gt;
&lt;li&gt;🔗  API Call: Apps Script fetches data from our API.&lt;/li&gt;
&lt;li&gt;🔐  Security: API is secured using an App Key.&lt;/li&gt;
&lt;li&gt;📥  Google Sheets Update: Data is parsed and inserted into Google Sheets.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;🗂️ Flow Diagram:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;⏳ Trigger (Google Apps Script) → 🔗 API Request → 🔐 Secure via App Key → 📥 Insert Data into Google Sheets&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏛️ Database Architecture Considerations&lt;/strong&gt;&lt;br&gt;
To optimize performance, we use Materialized Views in our database. This helps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;⚡ Reduce direct DB load.&lt;/li&gt;
&lt;li&gt;🚀 Speed up query execution.&lt;/li&gt;
&lt;li&gt;📊 Improve dashboard response times&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;🎯 Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using Google Sheets as a semi-automated dashboard is an effective and low-cost solution for handling candidate data. With minimal coding effort, we can keep data fresh and provide a functional dashboard without investing in a full-fledged UI-based system. This approach ensures scalability while keeping infrastructure costs low. ✅📉💡&lt;/p&gt;

</description>
      <category>automation</category>
      <category>javascript</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Custom Multipart Request in Dart for Video Uploads</title>
      <dc:creator>Saurabh Saha</dc:creator>
      <pubDate>Thu, 30 Jan 2025 20:34:01 +0000</pubDate>
      <link>https://dev.to/saurabh_saha_bf5fd7abb989/custom-multipart-request-in-dart-for-video-uploads-38c7</link>
      <guid>https://dev.to/saurabh_saha_bf5fd7abb989/custom-multipart-request-in-dart-for-video-uploads-38c7</guid>
      <description>&lt;p&gt;When implementing video uploads from Camera in our Flutter web application, we initially used &lt;strong&gt;http.MultipartRequest&lt;/strong&gt; to send the video file. However, we ran into multiple issues with the backend, which was using &lt;strong&gt;Express.js + Multer&lt;/strong&gt; for handling file uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initial Dart Code (That Didn't Work)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  final request = http.MultipartRequest('POST', uri);
  request.headers.addAll(headers);
  request.files.add(await http.MultipartFile.fromBytes(fileType, 
 bytes, contentType: mediaType));
  final response = await request.send();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NodeJS Multer Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our backend was using Express.js with Multer to handle the video uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const upload = multer({
    dest: 'uploads/',
    limits: {
        fileSize: 100 * 1024 * 1024, // ✅ Set max file size to 100MB
        fieldSize: 25 * 1024 * 1024, // ✅ Increase field size limit (for form fields)
    }
});

app.post('/upload', upload.single('videoResume'), (req, res) =&amp;gt; {
    if (!req.file) {
        return res.status(400).send('No file uploaded.');
    }

    console.log("File uploaded: ${req.file.originalname}");
    res.status(200).send('File uploaded successfully.');
});

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Issues Faced with Multer in Node.js&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1️⃣ File Length Too Long Issue&lt;/strong&gt;&lt;br&gt;
Multer would sometimes reject the request, stating that the file was too long or exceeding expected limits.&lt;/p&gt;

&lt;p&gt;Even after increasing fileSize in Multer's configuration, the issue persisted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2️⃣ req.file Undefined Issue&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multer did not recognize the uploaded file at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;req.file&lt;/strong&gt; was undefined, meaning the file wasn't being parsed correctly.&lt;/p&gt;

&lt;p&gt;The request headers and structure seemed correct, yet the backend couldn't extract the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution: Writing a Custom Multipart Request&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After multiple failed attempts we decided to manually construct the multipart request, ensuring that: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ The file is properly wrapped in a multipart boundary. &lt;/li&gt;
&lt;li&gt;✅ The correct Content-Type is used. &lt;/li&gt;
&lt;li&gt;✅ The request format is exactly as expected by the backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Final Working Dart Code (Manual Multipart Request)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;dynamic&amp;gt; multipartRequestWebVideo(
    String url, XFile videoFile, String fileType) async {
  try {
    final bytes = await videoFile.readAsBytes();
    final boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'; // Random boundary string
    final uri = Uri.parse(url);

    // Start building the body
    String body = '';
    body += '--$boundary\r\n';
    body += 'Content-Disposition: form-data; name="$fileType"; filename="${videoFile.name}"\r\n';
    body += 'Content-Type: video/webm\r\n\r\n';

    // Convert the body to a Uint8List (for binary data)
    List&amp;lt;int&amp;gt; bodyBytes = []..addAll(utf8.encode(body))..addAll(bytes);

    // Append the closing boundary
    bodyBytes.addAll(utf8.encode('\r\n--$boundary--\r\n'));

    // Get headers from our function (which adds auth tokens, etc.)
    var headers = await addHeaders(isMultiPart: true, isStream: true);

    // Override necessary headers
    headers['Content-Type'] = 'multipart/form-data; boundary=$boundary';
    headers['Content-Length'] = bodyBytes.length.toString();

    // Send the request
    final response = await http.post(
      uri,
      headers: headers,
      body: bodyBytes,
    );
  } catch (e) {
    return {
      'error': e.toString(),
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the limited time we had, we decided to create a custom multipart request from scratch. &lt;/p&gt;

</description>
      <category>flutter</category>
      <category>node</category>
      <category>dart</category>
      <category>multer</category>
    </item>
    <item>
      <title>Dynamically Generating Interfaces and Validation Schemas in TypeScript with Yup</title>
      <dc:creator>Saurabh Saha</dc:creator>
      <pubDate>Fri, 24 Jan 2025 08:03:26 +0000</pubDate>
      <link>https://dev.to/saurabh_saha_bf5fd7abb989/dynamically-generating-interfaces-and-validation-schemas-in-typescript-with-yup-2ioi</link>
      <guid>https://dev.to/saurabh_saha_bf5fd7abb989/dynamically-generating-interfaces-and-validation-schemas-in-typescript-with-yup-2ioi</guid>
      <description>&lt;p&gt;In a recent project, I encountered a problem where we needed to validate an object with keys dynamically defined by a constant map and enforce that at least one key has a valid value. &lt;/p&gt;

&lt;p&gt;The Challenge&lt;/p&gt;

&lt;p&gt;We had a MetadataMap object that defined valid keys and their corresponding types:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const MetadataMap = {&lt;br&gt;
    userId: Number,&lt;br&gt;
    utmSource: String,&lt;br&gt;
    utmMedium: String,&lt;br&gt;
    utmCampaign: String,&lt;br&gt;
} as const;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;From this map, we needed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dynamically generate a TypeScript interface to enforce type safety.&lt;/li&gt;
&lt;li&gt;Create a Yup validation schema that validates the object based on the map.&lt;/li&gt;
&lt;li&gt;Ensure at least one key in the object has a valid, non-undefined value.&lt;/li&gt;
&lt;li&gt;Avoid hardcoding keys to make the solution maintainable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But, TypeScript enforces static types at compile time, while Yup handles runtime validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Generating the Interface&lt;/strong&gt;&lt;br&gt;
To generate the TypeScript interface from the MetadataMap, we used keyof and mapped types. Here’s how we defined it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;type Metadata = {&lt;br&gt;
    [K in keyof typeof MetadataMap]: typeof MetadataMap[K] extends NumberConstructor&lt;br&gt;
        ? number&lt;br&gt;
        : string;&lt;br&gt;
};&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This approach ensured that any updates to MetadataMap were automatically reflected in the Metadata interface. For example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;// Resulting Metadata interface:&lt;br&gt;
interface Metadata {&lt;br&gt;
    userId?: number;&lt;br&gt;
    utmSource?: string;&lt;br&gt;
    utmMedium?: string;&lt;br&gt;
    utmCampaign?: string;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Dynamically Generating the Yup Schema&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We needed to dynamically create a Yup schema that matched the keys and types in MetadataMap. Using Object.keys and a reducer, we mapped each key to its corresponding Yup validator:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const metadataSchema = Yup.object(&lt;br&gt;
    Object.keys(MetadataMap).reduce((schema, key) =&amp;gt; {&lt;br&gt;
        const type = MetadataMap[key as keyof typeof MetadataMap];&lt;br&gt;
        if (type === Number) {&lt;br&gt;
            schema[key] = Yup.number().optional();&lt;br&gt;
        } else if (type === String) {&lt;br&gt;
            schema[key] = Yup.string().optional();&lt;br&gt;
        }&lt;br&gt;
        return schema;&lt;br&gt;
    }, {} as Record&amp;lt;string, any&amp;gt;)&lt;br&gt;
);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This method eliminated hardcoding and ensured that changes in MetadataMap were reflected in the schema without manual updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Adding the “At Least One Key” Rule&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The next challenge was ensuring that at least one key in the object had a defined value. We added a .test method to the Yup schema:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;metadataSchema.test(&lt;br&gt;
    "at-least-one-key",&lt;br&gt;
    "Metadata must have at least one valid key.",&lt;br&gt;
    (value) =&amp;gt; {&lt;br&gt;
        if (!value || typeof value !== "object") return false;&lt;br&gt;
        const validKeys = Object.keys(MetadataMap) as (keyof typeof MetadataMap)[];&lt;br&gt;
        return validKeys.some((key) =&amp;gt; key in value &amp;amp;&amp;amp; value[key] !== undefined);&lt;br&gt;
    }&lt;br&gt;
);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This logic:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensures the object is valid.&lt;/li&gt;
&lt;li&gt;Extracts valid keys dynamically from MetadataMap.&lt;/li&gt;
&lt;li&gt;Verifies that at least one key has a non-undefined value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Result&lt;br&gt;
Here’s how the final schema behaves:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const exampleMetadata = {&lt;br&gt;
    userId: undefined,&lt;br&gt;
    utmSource: "google",&lt;br&gt;
    extraField: "invalid", // This key is ignored.&lt;br&gt;
};&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
&lt;code&gt;metadataSchema&lt;br&gt;
    .validate(exampleMetadata)&lt;br&gt;
    .then(() =&amp;gt; console.log("Validation succeeded"))&lt;br&gt;
    .catch((err) =&amp;gt; console.error("Validation failed:", err.errors));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this example, validation succeeds because utmSource is a valid key with a non-undefined value, even though userId is undefined and extraField is not part of MetadataMap.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>yup</category>
    </item>
    <item>
      <title>Hellow World : Coder , freelancer</title>
      <dc:creator>Saurabh Saha</dc:creator>
      <pubDate>Tue, 21 Jan 2025 07:12:10 +0000</pubDate>
      <link>https://dev.to/saurabh_saha_bf5fd7abb989/hellow-world-coder-freelancer-3mjj</link>
      <guid>https://dev.to/saurabh_saha_bf5fd7abb989/hellow-world-coder-freelancer-3mjj</guid>
      <description>&lt;p&gt;Hello world. Seems almost right to say that. I am a coder :) would love to hear stories from other coders how they spend their day ? After 10 years of corporate work u am freelancing for 1 company now. The pay heck if 1/4th of what I earned but it’s satisfactory.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>python</category>
    </item>
  </channel>
</rss>
