<?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: Frank Joseph</title>
    <description>The latest articles on DEV Community by Frank Joseph (@frankdev20).</description>
    <link>https://dev.to/frankdev20</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%2F1016155%2F39519f26-abbd-4aa6-a596-ea45aa979894.jpeg</url>
      <title>DEV Community: Frank Joseph</title>
      <link>https://dev.to/frankdev20</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/frankdev20"/>
    <language>en</language>
    <item>
      <title>Mastra agent and Telex equals super integration</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Tue, 04 Nov 2025 02:48:55 +0000</pubDate>
      <link>https://dev.to/frankdev20/mastra-agent-and-telex-equals-super-integration-3149</link>
      <guid>https://dev.to/frankdev20/mastra-agent-and-telex-equals-super-integration-3149</guid>
      <description>&lt;p&gt;Building agent workflow has never been easy. However Mastra and Telex and are changing that narrative.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll walk you through how I integrated a Mastra AI Agent with Telex, using A2A (App-to-App) requests to enable smooth communication between the two platforms.&lt;/p&gt;

&lt;p&gt;Telex is an educational platform similar to Slack, it supports apps and agents that interact using messages.&lt;br&gt;
Mastra, on the other hand, is a modern framework for building, deploying, and scaling AI agents in production.&lt;/p&gt;

&lt;p&gt;By combining the two, we can create a setup where Telex sends structured messages to Mastra, Mastra processes them intelligently using our agent, and then sends a meaningful response back, all through JSON-RPC.&lt;/p&gt;

&lt;p&gt;Installing and Setting Up Mastra&lt;br&gt;
Before writing any code, we’ll need to set up Mastra locally.&lt;br&gt;
You can always check the official docs &lt;a href="https://mastra.ai/docs/getting-started/installation" rel="noopener noreferrer"&gt;Mastra Getting Started&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Create and initialize your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir weather-agent
cd weather-agent
npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Install Mastra&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @mastra/core @mastra/agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Define your Agent&lt;/p&gt;

&lt;p&gt;Create a file src/mastra/agents/weather-agent.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
import { weatherTool } from '../tools/weather-tool.js';

export const weatherAgent = new Agent({
  name: 'Weather Agent',
  instructions: `
      You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather

      Use the weatherTool to fetch current weather data.
`,
  model: 'google/gemini-2.5-pro',
  tools: { weatherTool },

  memory: new Memory({
    storage: new LibSQLStore({
      url: 'file:../mastra.db', // path is relative to the .mastra/output directory
    }),
  }),
});

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

&lt;/div&gt;



&lt;p&gt;Understanding the A2A Request Format&lt;/p&gt;

&lt;p&gt;When Telex sends data to an AI agent, it follows a strict JSON-RPC 2.0 format.&lt;br&gt;
Here’s what a sample Telex request looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "jsonrpc": "2.0",
  "id": "request-001",
  "method": "message/send",
  "params": {
    "message": {
      "kind": "message",
      "role": "user",
      "parts": [
        {
          "kind": "text",
          "text": "What's the weather condition"
        }
      ],
      "messageId": "msg-001"
    },
    "configuration": {
      "blocking": true
    }
  }
}

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

&lt;/div&gt;



&lt;p&gt;It is importatnt to make sure Mastra understands this format, extract the relevant message, and then send it to the agent for processing.&lt;/p&gt;

&lt;p&gt;Setting Up the Custom A2A Endpoint&lt;/p&gt;

&lt;p&gt;Mastra allows you to extend its API by registering custom routes.&lt;br&gt;
We created one called /a2a/agent/:agentId to handle requests from Telex.&lt;/p&gt;

&lt;p&gt;Here’s the full code for a2aRouter.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { registerApiRoute } from '@mastra/core/server';
import { randomUUID } from 'crypto';

interface A2AMessagePart {
  kind: 'text' | 'data';
  text?: string;
  data?: any;
}

interface A2AMessage {
  role: string;
  parts: A2AMessagePart[];
  messageId?: string;
  taskId?: string;
}

export const a2aAgentRoute = registerApiRoute('/a2a/agent/:agentId', {
  method: 'POST',
  handler: async (c: any) =&amp;gt; {
    try {
      const mastra = c.get('mastra');
      const agentId = c.req.param('agentId');

      // Parse JSON-RPC 2.0 request
      const body = await c.req.json();
      const { jsonrpc, id: requestId, method, params } = body;

      // Validate JSON-RPC 2.0 format
      if (jsonrpc !== '2.0' || !requestId) {
        return c.json(
          {
            jsonrpc: '2.0',
            id: requestId || null,
            error: {
              code: -32600,
              message: 'Invalid Request: jsonrpc must be "2.0" and id is required'
            }
          },
          400
        );
      }

      const agent = mastra.getAgent(agentId);
      if (!agent) {
        return c.json(
          {
            jsonrpc: '2.0',
            id: requestId,
            error: {
              code: -32602,
              message: `Agent '${agentId}' not found`
            }
          },
          404
        );
      }

      // Extract messages
      const { message, messages, contextId, taskId, metadata } = params || {};

      const messagesList: A2AMessage[] = message
        ? [message]
        : Array.isArray(messages)
        ? messages
        : [];

      // Convert A2A messages to Mastra format
      const mastraMessages = messagesList.map((msg) =&amp;gt; ({
        role: msg.role,
        content:
          msg.parts
            ?.map((part) =&amp;gt; {
              if (part.kind === 'text') return part.text || '';
              if (part.kind === 'data') return JSON.stringify(part.data || {});
              return '';
            })
            .join('\n') || ''
      }));

      // Execute agent
      const response = await agent.generate(mastraMessages);
      const agentText = response.text || '';

      // Build artifacts
      const artifacts = [
        {
          artifactId: randomUUID(),
          name: `${agentId}Response`,
          parts: [{ kind: 'text', text: agentText }]
        }
      ];

      if (response.toolResults?.length) {
        artifacts.push({
          artifactId: randomUUID(),
          name: 'ToolResults',
          parts: response.toolResults.map((result: any) =&amp;gt; ({
            kind: 'data',
            data: result
          }))
        });
      }

      // Build conversation history
      const history = [
        ...messagesList.map((msg) =&amp;gt; ({
          kind: 'message',
          role: msg.role,
          parts: msg.parts,
          messageId: msg.messageId || randomUUID(),
          taskId: msg.taskId || taskId || randomUUID()
        })),
        {
          kind: 'message',
          role: 'agent',
          parts: [{ kind: 'text', text: agentText }],
          messageId: randomUUID(),
          taskId: taskId || randomUUID()
        }
      ];

      // Return A2A-compliant response
      return c.json({
        jsonrpc: '2.0',
        id: requestId,
        result: {
          id: taskId || randomUUID(),
          contextId: contextId || randomUUID(),
          status: {
            state: 'completed',
            timestamp: new Date().toISOString(),
            message: {
              messageId: randomUUID(),
              role: 'agent',
              parts: [{ kind: 'text', text: agentText }],
              kind: 'message'
            }
          },
          artifacts,
          history,
          kind: 'task'
        }
      });
    } catch (error: any) {
      return c.json(
        {
          jsonrpc: '2.0',
          id: null,
          error: {
            code: -32603,
            message: 'Internal error',
            data: { details: error?.message || String(error) }
          }
        },
        500
      );
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linking the Route to Mastra&lt;/p&gt;

&lt;p&gt;In the mastra/index.ts, register the new route 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;
import { Mastra } from '@mastra/core/mastra';
import { PinoLogger } from '@mastra/loggers';
import { LibSQLStore } from '@mastra/libsql';
import { weatherWorkflow } from './workflows/weather-workflow.js';
import { weatherAgent } from './agents/weather-agent.js';
import { a2aAgentRoute } from '../mastra/routes/a2a-agent-route.js';


export const mastra = new Mastra({
  workflows: { weatherWorkflow },
  agents: { weatherAgent },
  storage: new LibSQLStore({
    // stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
    url: ":memory:",
  }),
  logger: new PinoLogger({
    name: 'Mastra',
    level: 'info',
  }),
  telemetry: {
    // Telemetry is deprecated and will be removed in the Nov 4th release
    enabled: false, 
  },
  observability: {
    // Enables DefaultExporter and CloudExporter for AI tracing
    default: { enabled: true }, 
  },

   server: {
    build: {
      openAPIDocs: true,
      swaggerUI: true,
    },
    apiRoutes: [
      a2aAgentRoute
    ]
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deployment to Mastra Cloud&lt;br&gt;
Once you’ve created your project and agent locally, simply push:&lt;/p&gt;

&lt;p&gt;npx mastra deploy&lt;br&gt;
After deployment, you’ll get a public URL like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://white-cloud-noon.mastra.cloud/a2a/agent/weatherAgent" rel="noopener noreferrer"&gt;https://white-cloud-noon.mastra.cloud/a2a/agent/weatherAgent&lt;/a&gt;&lt;br&gt;
This is the endpoint you’ll register on Telex under your A2A app configuration.&lt;/p&gt;

&lt;p&gt;Now, Telex will send messages directly to your agent and receive responses in real time.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>typescript</category>
      <category>agents</category>
    </item>
    <item>
      <title>HNG13 Stage 0 Task</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Fri, 17 Oct 2025 00:23:08 +0000</pubDate>
      <link>https://dev.to/frankdev20/hng13-stage-0-task-1cp2</link>
      <guid>https://dev.to/frankdev20/hng13-stage-0-task-1cp2</guid>
      <description>&lt;h2&gt;
  
  
  Update: I joined the HNG13 internship by Hotels.ng.
&lt;/h2&gt;

&lt;p&gt;Here is a break down of the stage 0 task and what I learned doing this task.&lt;br&gt;
The task required me to build a RESTful API endpoint that returns my profile information along with a dynamic cat fact fetched from an external API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "status": "success",&lt;br&gt;
  "user": {&lt;br&gt;
    "email": "&amp;lt;your email&amp;gt;",&lt;br&gt;
    "name": "&amp;lt;your full name&amp;gt;",&lt;br&gt;
    "stack": "&amp;lt;your backend stack&amp;gt;"&lt;br&gt;
  },&lt;br&gt;
  "timestamp": "&amp;lt;current UTC time in ISO 8601 format&amp;gt;",&lt;br&gt;
  "fact": "&amp;lt;random cat fact from Cat Facts API&amp;gt;"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The response must be in the JSON format above.&lt;br&gt;
The fact is fetched from this source: &lt;a href="https://catfact.ninja/fact" rel="noopener noreferrer"&gt;catfact&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here are the project specification:&lt;/p&gt;

&lt;h3&gt;
  
  
  Field specifications:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;status — Must always be the string "success"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user.email — Your personal email address&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user.name — Your full name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user.stack — Your backend technology stack (e.g., "Node.js/Express", "Python/Django", "Go/Gin"). I used Node.js/Express for my project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;timestamp — Current UTC time in ISO 8601 format (e.g., "2025-10-15T12:34:56.789Z")&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fact — A random cat fact fetched from the Cat Facts API&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's simple project, However, I learned a few things working on this project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I learned to use express-rate-limit package&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hosted my API on railway.com. I encountered a few challenges using railway as it was my time. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split my project into server.js and app.js. (Application server logic lives inside the server.js file while the main application logic, middlewares, cors, and rate limiting logic lives inside the app.js file)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this project, I learned to fetch resources from external APIs and handle errors gracefully when the API fails.&lt;/p&gt;

&lt;p&gt;Visit this github repository to see the full code and README file. &lt;a href="https://github.com/Frank-dev20/hng13_cat_api/tree/main" rel="noopener noreferrer"&gt;Code&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1f1qlg79y6k4z35xbhqm.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%2F1f1qlg79y6k4z35xbhqm.PNG" alt="CatFact" width="800" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>node</category>
    </item>
    <item>
      <title>Building a PDF-to-Markdown Converter Using Cardinal API</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Thu, 16 Oct 2025 23:53:20 +0000</pubDate>
      <link>https://dev.to/frankdev20/building-a-pdf-to-markdown-converter-using-cardinal-api-31o4</link>
      <guid>https://dev.to/frankdev20/building-a-pdf-to-markdown-converter-using-cardinal-api-31o4</guid>
      <description>&lt;p&gt;Document processing involves the processes or methods and technologies used to capture, digitize, extract, and transform information from both physical and digital documents into structured, machine-readable data.&lt;/p&gt;

&lt;p&gt;Document processing is an everyday activity for startups. Depending on the workflow and the volume of the document, processing documents could be tedious and time-consuming tasks. To successfully convert a document  from one form to another while preserving its structure requires thoughtful planning and the right document-processing solution. In this article, we'll explore a state of the art AI powered document processing software; Cardinal API, look at some of the processing tasks that can be performed with Cardinal API and show a demo solution using the Cardinal API to build a PDF to Markdown converter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Cardinal API?
&lt;/h3&gt;

&lt;p&gt;According to Y Combinator &lt;a href="https://trycardinal.ai/" rel="noopener noreferrer"&gt;Cardinal&lt;/a&gt; is the most accurate document processing API for Healthcare.&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%2Ftk8i7fxcw4n5evi6vgsv.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%2Ftk8i7fxcw4n5evi6vgsv.PNG" alt="Y Combinator LinkedIn Post" width="548" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cardinal API in simple terms gives team the ability to transform any document into structured data. At the time of writing this, here are the list of accepted files types Cardinal API currently process: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.pdf&lt;/li&gt;
&lt;li&gt;.jpeg/jpg&lt;/li&gt;
&lt;li&gt;.png&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 While Cardinal does not natively support Excel (.xls / .xlsx) or CSV (.csv) files, you can convert them to PDF on your own and process them through Cardinal.&lt;/p&gt;

&lt;p&gt;The following formats are not yet supported but are planned for future releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.doc / .docx — Microsoft Word&lt;/li&gt;
&lt;li&gt;.tiff — Tagged Image File Format&lt;/li&gt;
&lt;li&gt;.odt / .ott — OpenDocument Text &amp;amp; Template&lt;/li&gt;
&lt;li&gt;.rtf — Rich Text Format&lt;/li&gt;
&lt;li&gt;.txt — Plain Text&lt;/li&gt;
&lt;li&gt;.html / .htm — HTML Document&lt;/li&gt;
&lt;li&gt;.xml — XML Document&lt;/li&gt;
&lt;li&gt;.wps — Microsoft Works Word Processor&lt;/li&gt;
&lt;li&gt;.wpd — WordPerfect Document&lt;/li&gt;
&lt;li&gt;.ods / .ots — OpenDocument Spreadsheet &amp;amp; Template&lt;/li&gt;
&lt;li&gt;.ppt / .pptx — Microsoft PowerPoint&lt;/li&gt;
&lt;li&gt;.odp / .otp — OpenDocument Presentation &amp;amp; Template&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use cases for Cardinal API
&lt;/h3&gt;

&lt;p&gt;Here are some use cases for Cardinal API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/prior-auth" rel="noopener noreferrer"&gt;Prior Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/medical-coding" rel="noopener noreferrer"&gt;Medical Coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/rcm" rel="noopener noreferrer"&gt;Revenue Cycle Management (RCM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/ehr-integration" rel="noopener noreferrer"&gt;Electronic Health Record (EHR) Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to build A PDF to Markdown Converter using Cardinal API
&lt;/h3&gt;

&lt;p&gt;To use the Cardinal API, you must: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account&lt;/li&gt;
&lt;li&gt;Have an API key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do the above, visit this &lt;a href="https://dashboard.trycardinal.ai/login?redirect=%2F" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;br&gt;
Login with your Gmail or with your company email&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%2Fnlog82nn5ku84z9pvxwq.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%2Fnlog82nn5ku84z9pvxwq.PNG" alt=" " width="409" height="442"&gt;&lt;/a&gt;&lt;br&gt;
After you are one with the steps above, you'll be rerouted to your Cardinal dashboard&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%2Fbags37bowlx20gz0r458.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%2Fbags37bowlx20gz0r458.PNG" alt=" " width="607" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, you can request a free Cardinal API key. The free API key  gives you access to 50 free requests. That's a lot to get started. &lt;br&gt;
To request a free API key, type your preferred API key name in the text area as shown in your dashboard above and click on the Add Key button.&lt;/p&gt;

&lt;p&gt;However, if you want to take things a step further you can subscribe to&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a PDF-to-Markdown Converter Using Cardinal API</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Tue, 30 Sep 2025 16:07:10 +0000</pubDate>
      <link>https://dev.to/frankdev20/building-a-pdf-to-markdown-converter-using-cardinal-api-51jh</link>
      <guid>https://dev.to/frankdev20/building-a-pdf-to-markdown-converter-using-cardinal-api-51jh</guid>
      <description>&lt;p&gt;Document processing involves the processes or methods and technologies used to capture, digitize, extract, and transform information from both physical and digital documents into structured, machine-readable data.&lt;/p&gt;

&lt;p&gt;Document processing is an everyday activity for startups. Depending on the workflow and the volume of the document, processing documents could be tedious and time-consuming tasks. To successfully convert a document  from one form to another while preserving its structure requires thoughtful planning and the right document-processing solution. In this article, we'll explore a state of the art AI powered document processing software; Cardinal API, look at some of the processing tasks that can be performed with Cardinal API and show a demo solution using the Cardinal API to build a PDF to Markdown converter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Cardinal API?
&lt;/h3&gt;

&lt;p&gt;According to Y Combinator &lt;a href="https://trycardinal.ai/" rel="noopener noreferrer"&gt;Cardinal&lt;/a&gt; is the most accurate document processing API for Healthcare.&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%2Ftk8i7fxcw4n5evi6vgsv.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%2Ftk8i7fxcw4n5evi6vgsv.PNG" alt="Y Combinator LinkedIn Post" width="548" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cardinal API in simple terms gives team the ability to transform any document into structured data. At the time of writing this, here are the list of accepted files types Cardinal API currently process: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.pdf&lt;/li&gt;
&lt;li&gt;.jpeg/jpg&lt;/li&gt;
&lt;li&gt;.png&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 While Cardinal does not natively support Excel (.xls / .xlsx) or CSV (.csv) files, you can convert them to PDF on your own and process them through Cardinal.&lt;/p&gt;

&lt;p&gt;The following formats are not yet supported but are planned for future releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.doc / .docx — Microsoft Word&lt;/li&gt;
&lt;li&gt;.tiff — Tagged Image File Format&lt;/li&gt;
&lt;li&gt;.odt / .ott — OpenDocument Text &amp;amp; Template&lt;/li&gt;
&lt;li&gt;.rtf — Rich Text Format&lt;/li&gt;
&lt;li&gt;.txt — Plain Text&lt;/li&gt;
&lt;li&gt;.html / .htm — HTML Document&lt;/li&gt;
&lt;li&gt;.xml — XML Document&lt;/li&gt;
&lt;li&gt;.wps — Microsoft Works Word Processor&lt;/li&gt;
&lt;li&gt;.wpd — WordPerfect Document&lt;/li&gt;
&lt;li&gt;.ods / .ots — OpenDocument Spreadsheet &amp;amp; Template&lt;/li&gt;
&lt;li&gt;.ppt / .pptx — Microsoft PowerPoint&lt;/li&gt;
&lt;li&gt;.odp / .otp — OpenDocument Presentation &amp;amp; Template&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use cases for Cardinal API
&lt;/h3&gt;

&lt;p&gt;Here are some use cases for Cardinal API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/prior-auth" rel="noopener noreferrer"&gt;Prior Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/medical-coding" rel="noopener noreferrer"&gt;Medical Coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/rcm" rel="noopener noreferrer"&gt;Revenue Cycle Management (RCM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.trycardinal.ai/recipes/ehr-integration" rel="noopener noreferrer"&gt;Electronic Health Record (EHR) Integration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to build A PDF to Markdown Converter using Cardinal API
&lt;/h3&gt;

&lt;p&gt;To use the Cardinal API, you must: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account&lt;/li&gt;
&lt;li&gt;Have an API key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do the above, visit this &lt;a href="https://dashboard.trycardinal.ai/login?redirect=%2F" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;br&gt;
Login with your Gmail or with your company email&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%2Fnlog82nn5ku84z9pvxwq.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%2Fnlog82nn5ku84z9pvxwq.PNG" alt="Login Page" width="409" height="442"&gt;&lt;/a&gt;&lt;br&gt;
After you done are one with the steps above, you'll be rerouted to your Cardinal dashboard&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%2Fbags37bowlx20gz0r458.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%2Fbags37bowlx20gz0r458.PNG" alt="Cardinal Dashboard" width="607" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, you can request a free Cardinal API key. The free API key  gives you access to 50 free requests. That's a lot to get started. &lt;br&gt;
To request a free API key, type your preferred API key name in the text area as shown in your dashboard above and click on the Add Key button.&lt;/p&gt;

&lt;p&gt;However, if you want to take things a step further you can subscribe to a plan. &lt;a href="https://trycardinal.ai/pricing" rel="noopener noreferrer"&gt;Click here to see the pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that we cleared all the prerequisites, let's build our PDF to Markdown Converter. &lt;a href="https://cardinalapi-markdown-converter.netlify.app/" rel="noopener noreferrer"&gt;Here is a live version of our application&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Create a HTML file and write the following markup in it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
    &amp;lt;title&amp;gt;Cardinal API - Secure PDF/Image to Markdown Converter&amp;lt;/title&amp;gt;
    &amp;lt;link rel="stylesheet" href="style.css"&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
 &amp;lt;div class="container"&amp;gt;
        &amp;lt;div class="header"&amp;gt;
            &amp;lt;h1&amp;gt;🔐 Secure Cardinal API Converter&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Convert PDFs and images to clean Markdown with secure API key handling&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;!-- Security Notice --&amp;gt;
        &amp;lt;div class="security-notice"&amp;gt;
            &amp;lt;h4&amp;gt;🛡️ Security First&amp;lt;/h4&amp;gt;
            &amp;lt;p&amp;gt;Your API key is stored securely in your browser and never transmitted to any third parties. It's only sent directly to Cardinal's API for conversions.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;!-- API Key Setup --&amp;gt;
        &amp;lt;div class="api-key-setup"&amp;gt;
            &amp;lt;h2 style="margin-bottom: 15px;"&amp;gt;🔑 API Key Configuration&amp;lt;/h2&amp;gt;
            &amp;lt;p style="color: #666; margin-bottom: 15px;"&amp;gt;
                Enter your Cardinal API key to get started. You can get one from 
                &amp;lt;a href="https://dashboard.trycardinal.ai" target="_blank" style="color: #667eea;"&amp;gt;Cardinal Dashboard&amp;lt;/a&amp;gt;.
            &amp;lt;/p&amp;gt;

            &amp;lt;div class="api-key-input"&amp;gt;
                &amp;lt;input 
                    type="password" 
                    id="apiKeyInput" 
                    class="api-key-field" 
                    placeholder="Enter your Cardinal API key..."
                    autocomplete="off"
                &amp;gt;
                &amp;lt;button type="button" class="api-key-toggle" id="toggleApiKey"&amp;gt;Show&amp;lt;/button&amp;gt;
                &amp;lt;button type="button" class="api-key-save" id="saveApiKey"&amp;gt;Validate &amp;amp; Save&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div style="display: flex; align-items: center; gap: 10px; margin-top: 10px;"&amp;gt;
                &amp;lt;input type="checkbox" id="rememberApiKey"&amp;gt;
                &amp;lt;label for="rememberApiKey" style="font-size: 0.9rem; color: #666;"&amp;gt;
                    Remember API key (stored locally in your browser)
                &amp;lt;/label&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div id="apiStatus" class="api-status" style="display: none;"&amp;gt;&amp;lt;/div&amp;gt;

            &amp;lt;div style="margin-top: 15px; font-size: 0.8rem; color: #888;"&amp;gt;
                &amp;lt;p&amp;gt;💡 &amp;lt;strong&amp;gt;Tips:&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
                &amp;lt;ul style="margin-left: 20px; margin-top: 5px;"&amp;gt;
                    &amp;lt;li&amp;gt;API key is encrypted and stored only in your browser&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;You can clear stored keys anytime using the "Clear Keys" button&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;Keys are automatically validated before use&amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div style="margin-top: 15px;"&amp;gt;
                &amp;lt;button type="button" id="clearApiKeys" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 0.9rem;"&amp;gt;
                    🗑️ Clear Stored Keys
                &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;!-- Main Content (Initially Disabled) --&amp;gt;
        &amp;lt;div id="mainContent" class="content-disabled"&amp;gt;
            &amp;lt;!-- Options Section --&amp;gt;
            &amp;lt;div class="options-section"&amp;gt;
                &amp;lt;h2 class="section-title"&amp;gt;⚙️ Conversion Options&amp;lt;/h2&amp;gt;
                &amp;lt;div class="options-grid"&amp;gt;
                    &amp;lt;div class="option-item"&amp;gt;
                        &amp;lt;input type="checkbox" id="markdown" checked&amp;gt;
                        &amp;lt;label for="markdown"&amp;gt;Enable Markdown output&amp;lt;/label&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="option-item"&amp;gt;
                        &amp;lt;input type="checkbox" id="denseTables" checked&amp;gt;
                        &amp;lt;label for="denseTables"&amp;gt;Dense table formatting&amp;lt;/label&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="option-item"&amp;gt;
                        &amp;lt;input type="checkbox" id="preserveLayout" checked&amp;gt;
                        &amp;lt;label for="preserveLayout"&amp;gt;Preserve original layout&amp;lt;/label&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="option-item"&amp;gt;
                        &amp;lt;input type="checkbox" id="cleanText" checked&amp;gt;
                        &amp;lt;label for="cleanText"&amp;gt;Clean text output&amp;lt;/label&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;

            &amp;lt;div class="main-content"&amp;gt;
                &amp;lt;!-- File Upload Section --&amp;gt;
                &amp;lt;div class="upload-section"&amp;gt;
                    &amp;lt;h2 class="section-title"&amp;gt;📁 Upload Files&amp;lt;/h2&amp;gt;
                    &amp;lt;div id="upload-container"&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;

                &amp;lt;!-- URL Conversion Section --&amp;gt;
                &amp;lt;div class="url-section"&amp;gt;
                    &amp;lt;h2 class="section-title"&amp;gt;🔗 Convert from URL&amp;lt;/h2&amp;gt;
                    &amp;lt;div class="url-input-group"&amp;gt;
                        &amp;lt;input type="url" id="fileUrl" placeholder="https://example.com/document.pdf" class="url-input"&amp;gt;
                        &amp;lt;button id="convertUrl" class="convert-btn"&amp;gt;Convert&amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;p style="color: #666; font-size: 0.9rem;"&amp;gt;
                        Enter the URL of a PDF or image file to convert it to Markdown
                    &amp;lt;/p&amp;gt;
                    &amp;lt;div id="url-results"&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;!-- Results Container --&amp;gt;
        &amp;lt;div id="results-container"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
   &amp;lt;script src="index.js"&amp;gt;&amp;lt;/script&amp;gt;
 &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Step 2: Create a CSS (style.css) file and write the following styles in it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            line-height: 1.6;
            color: #333;
            background-color: #f8f9fa;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }

        .header {
            text-align: center;
            margin-bottom: 40px;
            padding: 40px 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border-radius: 12px;
            color: white;
        }

        .api-key-setup {
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.1);
            margin-bottom: 30px;
            border-left: 4px solid #667eea;
        }

        .api-key-input {
            display: flex;
            gap: 10px;
            margin: 15px 0;
            align-items: center;
        }

        .api-key-field {
            flex: 1;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
        }

        .api-key-toggle {
            background: #6c757d;
            color: white;
            border: none;
            padding: 12px 16px;
            border-radius: 6px;
            cursor: pointer;
            min-width: 80px;
        }

        .api-key-save {
            background: #28a745;
            color: white;
            border: none;
            padding: 12px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 500;
        }

        .api-key-save:disabled {
            background: #ccc;
            cursor: not-allowed;
        }

        .api-status {
            margin-top: 15px;
            padding: 10px;
            border-radius: 6px;
            font-size: 0.9rem;
        }

        .status-success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .status-error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        .status-info {
            background: #d1ecf1;
            color: #0c5460;
            border: 1px solid #bee5eb;
        }

        .main-content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 40px;
            margin-bottom: 40px;
        }

        .content-disabled {
            opacity: 0.5;
            pointer-events: none;
        }

        .upload-section, .url-section {
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.1);
        }

        .section-title {
            font-size: 1.4rem;
            margin-bottom: 20px;
            color: #2c3e50;
            border-bottom: 2px solid #e9ecef;
            padding-bottom: 10px;
        }

        .drop-zone {
            border: 2px dashed #ddd;
            border-radius: 12px;
            padding: 40px 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
            background: #fafbfc;
        }

        .drop-zone:hover, .drop-zone.drag-over {
            border-color: #667eea;
            background-color: #f0f3ff;
            transform: translateY(-2px);
        }

        .browse-btn {
            background: #667eea;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 1rem;
        }

        .url-input-group {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }

        .url-input {
            flex: 1;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 1rem;
        }

        .convert-btn {
            background: #48bb78;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 1rem;
        }

        .options-section {
            background: white;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.1);
            margin-bottom: 40px;
        }

        .options-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
        }

        .option-item {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .conversion-progress {
            margin-top: 20px;
            padding: 20px;
            border-radius: 8px;
            background: white;
            border-left: 4px solid #667eea;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }

        .progress-bar {
            width: 100%;
            height: 8px;
            background: #e9ecef;
            border-radius: 4px;
            overflow: hidden;
            margin: 15px 0;
        }

        .progress-bar-fill {
            height: 100%;
            background: linear-gradient(90deg, #48bb78, #38a169);
            width: 0%;
            transition: width 0.3s ease;
        }

        .security-notice {
            background: #fff3cd;
            border: 1px solid #ffeaa7;
            border-radius: 6px;
            padding: 15px;
            margin-bottom: 20px;
        }

        .security-notice h4 {
            color: #856404;
            margin-bottom: 5px;
        }

        .security-notice p {
            color: #856404;
            font-size: 0.9rem;
            margin: 0;
        }

        @media (max-width: 768px) {
            .main-content {
                grid-template-columns: 1fr;
            }

            .api-key-input {
                flex-direction: column;
                align-items: stretch;
            }
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Step 3: Create a JavaScript (index.js) file and write the following code in it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Secure Cardinal API Converter Class
        class SecureCardinalConverter {
            constructor() {
                this.apiKey = null;
                this.baseUrl = 'https://api.trycardinal.ai';
                this.isValidated = false;
            }

            setApiKey(apiKey) {
                if (!apiKey || apiKey.trim() === '') {
                    throw new Error('API key is required');
                }
                this.apiKey = apiKey.trim();
                this.isValidated = false;
            }

            async validateApiKey() {
                if (!this.apiKey) {
                    throw new Error('API key not set');
                }

                try {
                    // Test with a lightweight request
                    const response = await fetch(`${this.baseUrl}/markdown`, {
                        method: 'POST',
                        headers: { 
                            'x-api-key': this.apiKey,
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            fileUrl: 'https://httpbin.org/status/404', // This will fail but validates auth
                            markdown: true
                        })
                    });

                    // API key is valid if we don't get 401/403
                    this.isValidated = ![401, 403].includes(response.status);
                    return this.isValidated;
                } catch (error) {
                    console.warn('API validation error:', error);
                    return false;
                }
            }

            async convertFile(file, options = {}) {
                if (!this.apiKey || !this.isValidated) {
                    throw new Error('Please validate your API key first');
                }

                const formData = new FormData();
                formData.append('file', file);

                Object.keys(options).forEach(key =&amp;gt; {
                    formData.append(key, options[key].toString());
                });

                const response = await fetch(`${this.baseUrl}/markdown`, {
                    method: 'POST',
                    headers: { 'x-api-key': this.apiKey },
                    body: formData
                });

                if (!response.ok) {
                    if (response.status === 401 || response.status === 403) {
                        this.isValidated = false;
                        throw new Error('API key is invalid or expired. Please check your key.');
                    }
                    throw new Error(`API request failed: ${response.status} ${response.statusText}`);
                }

                const result = await response.json();
                return result.pages?.[0]?.content || result.content || '';
            }

            async convertUrl(url, options = {}) {
                if (!this.apiKey || !this.isValidated) {
                    throw new Error('Please validate your API key first');
                }

                const response = await fetch(`${this.baseUrl}/markdown`, {
                    method: 'POST',
                    headers: {
                        'x-api-key': this.apiKey,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ fileUrl: url, ...options })
                });

                if (!response.ok) {
                    if (response.status === 401 || response.status === 403) {
                        this.isValidated = false;
                        throw new Error('API key is invalid or expired. Please check your key.');
                    }
                    throw new Error(`API request failed: ${response.status} ${response.statusText}`);
                }

                const result = await response.json();
                return result.pages?.[0]?.content || result.content || '';
            }

            validateFile(file) {
                const maxSize = 10 * 1024 * 1024;
                const allowedTypes = [
                    'application/pdf', 'image/jpeg', 'image/jpg', 
                    'image/png', 'image/gif', 'image/bmp', 'image/tiff'
                ];

                if (file.size &amp;gt; maxSize) {
                    throw new Error('File size exceeds 10MB limit');
                }

                if (!allowedTypes.includes(file.type)) {
                    throw new Error('Unsupported file type. Please use PDF or image files.');
                }
            }
        }

        // Secure Storage Utilities
        const SecureStorage = {
            // Simple encryption for local storage (basic obfuscation)
            encrypt(text) {
                return btoa(encodeURIComponent(text));
            },

            decrypt(encoded) {
                try {
                    return decodeURIComponent(atob(encoded));
                } catch {
                    return null;
                }
            },

            store(key, value, remember = false) {
                const storage = remember ? localStorage : sessionStorage;
                storage.setItem(key, this.encrypt(value));
            },

            retrieve(key) {
                // Check session storage first, then local storage
                let value = sessionStorage.getItem(key) || localStorage.getItem(key);
                return value ? this.decrypt(value) : null;
            },

            clear(key) {
                sessionStorage.removeItem(key);
                localStorage.removeItem(key);
            }
        };

        // Initialize Application
        document.addEventListener('DOMContentLoaded', function() {
            const converter = new SecureCardinalConverter();

            // DOM Elements
            const apiKeyInput = document.getElementById('apiKeyInput');
            const toggleApiKey = document.getElementById('toggleApiKey');
            const saveApiKey = document.getElementById('saveApiKey');
            const rememberApiKey = document.getElementById('rememberApiKey');
            const apiStatus = document.getElementById('apiStatus');
            const clearApiKeys = document.getElementById('clearApiKeys');
            const mainContent = document.getElementById('mainContent');

            // Load stored API key
            const storedApiKey = SecureStorage.retrieve('cardinal_api_key');
            if (storedApiKey) {
                apiKeyInput.value = storedApiKey;
                rememberApiKey.checked = localStorage.getItem('cardinal_api_key') !== null;
                validateApiKey(storedApiKey, false);
            }

            // Toggle API key visibility
            toggleApiKey.addEventListener('click', function() {
                const isPassword = apiKeyInput.type === 'password';
                apiKeyInput.type = isPassword ? 'text' : 'password';
                toggleApiKey.textContent = isPassword ? 'Hide' : 'Show';
            });

            // Save and validate API key
            saveApiKey.addEventListener('click', function() {
                const apiKey = apiKeyInput.value.trim();
                if (!apiKey) {
                    showStatus('Please enter an API key', 'error');
                    return;
                }
                validateApiKey(apiKey, true);
            });

            // Clear stored keys
            clearApiKeys.addEventListener('click', function() {
                if (confirm('Are you sure you want to clear all stored API keys?')) {
                    SecureStorage.clear('cardinal_api_key');
                    apiKeyInput.value = '';
                    rememberApiKey.checked = false;
                    showStatus('All stored API keys have been cleared', 'info');
                    mainContent.classList.add('content-disabled');
                }
            });

            // Enter key support
            apiKeyInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    saveApiKey.click();
                }
            });

            async function validateApiKey(apiKey, showFeedback = true) {
                if (showFeedback) {
                    saveApiKey.disabled = true;
                    saveApiKey.textContent = 'Validating...';
                    showStatus('Validating API key...', 'info');
                }

                try {
                    converter.setApiKey(apiKey);
                    const isValid = await converter.validateApiKey();

                    if (isValid) {
                        // Store the API key
                        SecureStorage.store('cardinal_api_key', apiKey, rememberApiKey.checked);

                        if (showFeedback) {
                            showStatus('✅ API key validated successfully!', 'success');
                        }

                        // Enable main content
                        mainContent.classList.remove('content-disabled');

                        // Initialize the converter interface
                        initializeConverterInterface();
                    } else {
                        throw new Error('Invalid API key or service unavailable');
                    }
                } catch (error) {
                    if (showFeedback) {
                        showStatus(`❌ ${error.message}`, 'error');
                    }
                    mainContent.classList.add('content-disabled');
                } finally {
                    if (showFeedback) {
                        saveApiKey.disabled = false;
                        saveApiKey.textContent = 'Validate &amp;amp; Save';
                    }
                }
            }

            function showStatus(message, type) {
                apiStatus.textContent = message;
                apiStatus.className = `api-status status-${type}`;
                apiStatus.style.display = 'block';

                if (type === 'success' || type === 'info') {
                    setTimeout(() =&amp;gt; {
                        apiStatus.style.display = 'none';
                    }, 5000);
                }
            }

            function initializeConverterInterface() {
                // Initialize file upload interface
                createDropZone();
                setupUrlConversion();
            }

            // Rest of the converter interface code (same as before but using the secure converter)
            function getConversionOptions() {
                return {
                    markdown: document.getElementById('markdown').checked,
                    denseTables: document.getElementById('denseTables').checked,
                    preserveLayout: document.getElementById('preserveLayout').checked,
                    cleanText: document.getElementById('cleanText').checked
                };
            }

            function createDropZone() {
                const container = document.getElementById('upload-container');
                container.innerHTML = ''; // Clear any existing content

                const fileInput = document.createElement('input');
                fileInput.type = 'file';
                fileInput.accept = '.pdf,image/*';
                fileInput.multiple = true;
                fileInput.style.display = 'none';

                const dropZone = document.createElement('div');
                dropZone.className = 'drop-zone';
                dropZone.innerHTML = `
                    &amp;lt;div class="drop-zone-content"&amp;gt;
                        &amp;lt;p&amp;gt;📄 Drop PDF or image files here or &amp;lt;button type="button" class="browse-btn"&amp;gt;browse&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;
                        &amp;lt;small&amp;gt;Supported formats: PDF, JPEG, PNG, GIF, BMP, TIFF (max 10MB each)&amp;lt;/small&amp;gt;
                    &amp;lt;/div&amp;gt;
                `;

                container.appendChild(dropZone);
                container.appendChild(fileInput);

                // Event listeners
                dropZone.addEventListener('click', () =&amp;gt; fileInput.click());
                dropZone.querySelector('.browse-btn').addEventListener('click', (e) =&amp;gt; {
                    e.stopPropagation();
                    fileInput.click();
                });

                // Drag and drop events
                ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName =&amp;gt; {
                    dropZone.addEventListener(eventName, preventDefaults);
                });

                function preventDefaults(e) {
                    e.preventDefault();
                    e.stopPropagation();
                }

                ['dragenter', 'dragover'].forEach(eventName =&amp;gt; {
                    dropZone.addEventListener(eventName, () =&amp;gt; dropZone.classList.add('drag-over'));
                });

                ['dragleave', 'drop'].forEach(eventName =&amp;gt; {
                    dropZone.addEventListener(eventName, () =&amp;gt; dropZone.classList.remove('drag-over'));
                });

                dropZone.addEventListener('drop', (e) =&amp;gt; handleFiles(e.dataTransfer.files));
                fileInput.addEventListener('change', (e) =&amp;gt; handleFiles(e.target.files));
            }

            async function handleFiles(files) {
                const fileList = Array.from(files);

                for (const file of fileList) {
                    try {
                        converter.validateFile(file);
                        await processFile(file);
                    } catch (error) {
                        showError(`Error with file "${file.name}": ${error.message}`);
                    }
                }
            }

            async function processFile(file) {
                const resultsContainer = document.getElementById('upload-container');

                const progressDiv = document.createElement('div');
                progressDiv.className = 'conversion-progress';
                progressDiv.innerHTML = `
                    &amp;lt;div style="display: flex; justify-content: space-between; align-items: center;"&amp;gt;
                        &amp;lt;strong&amp;gt;🔄 Converting: ${file.name}&amp;lt;/strong&amp;gt;
                        &amp;lt;span class="file-size"&amp;gt;${formatFileSize(file.size)}&amp;lt;/span&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="progress-bar"&amp;gt;
                        &amp;lt;div class="progress-bar-fill"&amp;gt;&amp;lt;/div&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="status"&amp;gt;Initializing conversion...&amp;lt;/div&amp;gt;
                `;
                resultsContainer.appendChild(progressDiv);

                const progressBar = progressDiv.querySelector('.progress-bar-fill');
                const statusText = progressDiv.querySelector('.status');

                try {
                    updateProgress(progressBar, statusText, 25, 'Uploading file...');

                    const options = getConversionOptions();
                    const markdown = await converter.convertFile(file, options);

                    updateProgress(progressBar, statusText, 100, 'Conversion completed!');

                    setTimeout(() =&amp;gt; {
                        displayResult(file.name, markdown, progressDiv);
                    }, 500);

                } catch (error) {
                    progressDiv.innerHTML = `
                        &amp;lt;div style="background: #f8d7da; color: #721c24; padding: 15px; border-radius: 8px; border-left: 4px solid #dc3545;"&amp;gt;
                            &amp;lt;strong&amp;gt;❌ Error converting ${file.name}&amp;lt;/strong&amp;gt;
                            &amp;lt;p&amp;gt;${error.message}&amp;lt;/p&amp;gt;
                            ${error.message.includes('API key') ? '&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Please check your API key configuration above.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;' : ''}
                        &amp;lt;/div&amp;gt;
                    `;
                }
            }

            function setupUrlConversion() {
                const urlInput = document.getElementById('fileUrl');
                const convertBtn = document.getElementById('convertUrl');
                const resultsDiv = document.getElementById('url-results');

                convertBtn.addEventListener('click', async () =&amp;gt; {
                    const url = urlInput.value.trim();

                    if (!url) {
                        showError('Please enter a valid URL');
                        return;
                    }

                    if (!isValidUrl(url)) {
                        showError('Please enter a valid URL starting with http:// or https://');
                        return;
                    }

                    convertBtn.disabled = true;
                    convertBtn.textContent = 'Converting...';
                    resultsDiv.innerHTML = '';

                    const progressDiv = document.createElement('div');
                    progressDiv.className = 'conversion-progress';
                    progressDiv.innerHTML = `
                        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;🔄 Converting URL: ${url}&amp;lt;/strong&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class="progress-bar"&amp;gt;
                            &amp;lt;div class="progress-bar-fill" style="width: 50%"&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;/div&amp;gt;
                        &amp;lt;div class="status"&amp;gt;Processing URL...&amp;lt;/div&amp;gt;
                    `;
                    resultsDiv.appendChild(progressDiv);

                    try {
                        const options = getConversionOptions();
                        const markdown = await converter.convertUrl(url, options);

                        const fileName = url.split('/').pop() || 'converted-document';
                        displayResult(fileName, markdown, progressDiv);

                    } catch (error) {
                        progressDiv.innerHTML = `
                            &amp;lt;div style="background: #f8d7da; color: #721c24; padding: 15px; border-radius: 8px; border-left: 4px solid #dc3545;"&amp;gt;
                                &amp;lt;strong&amp;gt;❌ Error converting URL&amp;lt;/strong&amp;gt;
                                &amp;lt;p&amp;gt;${error.message}&amp;lt;/p&amp;gt;
                                ${error.message.includes('API key') ? '&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Please check your API key configuration above.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;' : ''}
                            &amp;lt;/div&amp;gt;
                        `;
                    } finally {
                        convertBtn.disabled = false;
                        convertBtn.textContent = 'Convert';
                    }
                });

                urlInput.addEventListener('keypress', (e) =&amp;gt; {
                    if (e.key === 'Enter') {
                        convertBtn.click();
                    }
                });
            }

            function updateProgress(progressBar, statusText, progress, status) {
                progressBar.style.width = `${progress}%`;
                statusText.textContent = status;
            }

            function displayResult(fileName, markdown, container) {
                container.innerHTML = `
                    &amp;lt;div style="margin-bottom: 15px;"&amp;gt;
                        &amp;lt;strong&amp;gt;✅ ${fileName} - Conversion completed!&amp;lt;/strong&amp;gt;
                        &amp;lt;span style="color: #666; font-size: 0.9rem;"&amp;gt;(${formatFileSize(markdown.length)} of markdown)&amp;lt;/span&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div style="display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap;"&amp;gt;
                        &amp;lt;button class="copy-btn" style="background: #4299e1; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;"&amp;gt;📋 Copy to Clipboard&amp;lt;/button&amp;gt;
                        &amp;lt;button class="download-btn" style="background: #48bb78; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;"&amp;gt;💾 Download Markdown&amp;lt;/button&amp;gt;
                        &amp;lt;button class="view-btn" style="background: #ed8936; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;"&amp;gt;👁️ View Content&amp;lt;/button&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div class="markdown-preview" style="display: none; background: #f8f9fa; padding: 20px; border-radius: 8px; max-height: 400px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 0.9rem; white-space: pre-wrap; border: 1px solid #e9ecef;"&amp;gt;
                        &amp;lt;div style="margin-bottom: 10px; font-weight: bold; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;"&amp;gt;Markdown Content:&amp;lt;/div&amp;gt;${escapeHtml(markdown)}
                    &amp;lt;/div&amp;gt;
                `;

                const copyBtn = container.querySelector('.copy-btn');
                const downloadBtn = container.querySelector('.download-btn');
                const viewBtn = container.querySelector('.view-btn');
                const previewDiv = container.querySelector('.markdown-preview');

                copyBtn.addEventListener('click', async () =&amp;gt; {
                    try {
                        await navigator.clipboard.writeText(markdown);
                        copyBtn.innerHTML = '✅ Copied!';
                        setTimeout(() =&amp;gt; copyBtn.innerHTML = '📋 Copy to Clipboard', 2000);
                    } catch (err) {
                        showError('Failed to copy to clipboard');
                    }
                });

                downloadBtn.addEventListener('click', () =&amp;gt; {
                    downloadMarkdown(markdown, fileName);
                });

                viewBtn.addEventListener('click', () =&amp;gt; {
                    const isVisible = previewDiv.style.display !== 'none';
                    previewDiv.style.display = isVisible ? 'none' : 'block';
                    viewBtn.innerHTML = isVisible ? '👁️ View Content' : '🙈 Hide Content';
                });
            }

            function formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }

            function escapeHtml(text) {
                const div = document.createElement('div');
                div.textContent = text;
                return div.innerHTML;
            }

            function isValidUrl(string) {
                try {
                    const url = new URL(string);
                    return url.protocol === 'http:' || url.protocol === 'https:';
                } catch (_) {
                    return false;
                }
            }

            function downloadMarkdown(content, originalFileName) {
                const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `${originalFileName.replace(/\.[^/.]+$/, '')}.md`;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }

            function showError(message) {
                const errorDiv = document.createElement('div');
                errorDiv.style.cssText = `
                    background: #f8d7da; 
                    color: #721c24; 
                    padding: 15px; 
                    border-radius: 8px; 
                    margin-top: 15px; 
                    border-left: 4px solid #dc3545;
                `;
                errorDiv.innerHTML = `&amp;lt;strong&amp;gt;❌ Error:&amp;lt;/strong&amp;gt; ${message}`;

                const container = document.getElementById('results-container');
                container.appendChild(errorDiv);

                setTimeout(() =&amp;gt; {
                    if (errorDiv.parentNode) {
                        errorDiv.parentNode.removeChild(errorDiv);
                    }
                }, 5000);
            }
        });

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

&lt;/div&gt;



&lt;p&gt;At this point, we have a fully functional web application you can view on your browser. You should see something like this:&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%2Fg6cupick1iwokbe7fp4f.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%2Fg6cupick1iwokbe7fp4f.PNG" alt="Web page" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foc2xa7mzxygz5gop40e6.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%2Foc2xa7mzxygz5gop40e6.PNG" alt="Web page" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use this web app, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy and paste your API key in the API Key Configuration text area, - - Click the Validate and Save button&lt;/li&gt;
&lt;li&gt;Go to the Upload file and upload the file you want to convert to Markdown or use the URL conversion option.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to use the Python better_profanity Filter API with GraphQL</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Tue, 12 Nov 2024 10:14:32 +0000</pubDate>
      <link>https://dev.to/frankdev20/how-to-use-the-python-betterprofanity-filter-api-with-graphql-43if</link>
      <guid>https://dev.to/frankdev20/how-to-use-the-python-betterprofanity-filter-api-with-graphql-43if</guid>
      <description>&lt;p&gt;As social interaction on the web continues to grow especially as generative AIs continue to gain global adoption, there is an increasing need to build social applications with abilities to detect and filter profane words.&lt;/p&gt;

&lt;p&gt;Building applications that can detect and filter profanity could be one of the solutions to safer online social communication and interaction.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll illustrate with code snippets how to build a profanity filter using Python better_profanity API and GraphQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Profanity
&lt;/h3&gt;

&lt;p&gt;Profanity is the use of swear, rude, and offensive words in conversation. Profanity can be used to express a strong feeling of animosity or disapproval to someone or something.&lt;/p&gt;

&lt;p&gt;A profanity filter is an application that detects, and filters words perceived as profane in an online communication channel. &lt;/p&gt;

&lt;h3&gt;
  
  
  Reasons to detect and filter profanity
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;To make online spaces safe for social interaction.&lt;/li&gt;
&lt;li&gt;To automatically detect and filter unwanted content.&lt;/li&gt;
&lt;li&gt;Detecting and filtering profanity improves user experience.&lt;/li&gt;
&lt;li&gt;It builds healthy social spaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Detecting Profanity with better_profanity
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/better-profanity/" rel="noopener noreferrer"&gt;Better-profanity&lt;/a&gt; is a blazingly fast Python library to detect and clean swear words. It supports custom word lists, safelists, detecting profanity in modified word spellings, Unicode characters (also called leetspeak), and even multi-lingual profanity detection.&lt;/p&gt;

&lt;p&gt;To get started you'll need the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python installed on your machine&lt;/li&gt;
&lt;li&gt;Basic knowledge of Python&lt;/li&gt;
&lt;li&gt;Basic knowledge of GraphQL&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing better_profanity library
&lt;/h3&gt;

&lt;p&gt;To install the better_profanity library for our project, run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; pip install better_profanity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your Python project, create a &lt;em&gt;profanity_filter.py&lt;/em&gt; file and add the following code snippets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from better_profanity import profanity

profanity.load_censor_words()


def test_profanity(paragraph):
    print(profanity.censor(paragraph))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you pass an offensive word as an argument to the function, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_profanity("Don't be fuck")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You would get the following result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Don't be ****
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python &lt;em&gt;better_profanity&lt;/em&gt; has a function that tells whether a string contains a swear word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profanity.contains_profanity() # True | # False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python &lt;em&gt;better_profanity&lt;/em&gt; has a function that censors swear words with a custom character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profanity.censor(word, '-')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second argument in &lt;em&gt;.censor('-')&lt;/em&gt; will be used to censor any swear word found in the first argument (word).&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a GraphQL API for our Profanity Filter
&lt;/h3&gt;

&lt;p&gt;Now we have the profanity filter working, let's build a GraphQL API for our filter and test it. &lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Flask and GraphQL
&lt;/h3&gt;

&lt;p&gt;To install Flask and GraphQL libraries in our application, on your terminal, 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;pip install Flask Flask_GraphQL graphene
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll write our API GraphQl schema. To do that, create a &lt;em&gt;schema.py&lt;/em&gt; file and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import graphene
from better_profanity import profanity


class Outcome(graphene.ObjectType):
    paragraph = graphene.String()
    is_profane = graphene.Boolean()
    censored_paragraph = graphene.String()


class Query(graphene.ObjectType):
    detect_profanity = graphene.Field(Outcome, paragraph=graphene.String(
        required=True), character=graphene.String(default_value="*"))

    def resolve_detect_profanity(self, info, paragraph, character):
        is_profane = profanity.contains_profanity(paragraph)
        censored_paragraph = profanity.censor(paragraph, character)
        return Outcome(
            paragraph=paragraph,
            is_profane=is_profane,
            censored_paragraph=censored_paragraph
        )


profanity.load_censor_words()
schema = graphene.Schema(query=Query)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let's configure our profanity filter to a server with an accessible URL. To do that, create a Python file, &lt;em&gt;app.py&lt;/em&gt;, and add the following code to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from flask import Flask
from flask_graphql import GraphQLView
from schema import schema

app = Flask(__name__)
app.add_url_rule("/", view_func=GraphQLView.as_view("graphql",
                 schema=schema, graphiql=True))


if __name__ == "__main__":
    app.run(debug=True)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run our application, run the following command in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you do everything right, the server should start running, and your terminal should be like the one in the attached image below:&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%2Fffgmh0gqe098swnaijjv.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%2Fffgmh0gqe098swnaijjv.PNG" alt="Server_running" width="800" height="124"&gt;&lt;/a&gt;&lt;br&gt;
Now you can test your API by visiting this URL (&lt;a href="http://127.0.0.1:5000/" rel="noopener noreferrer"&gt;http://127.0.0.1:5000/&lt;/a&gt;) as shown on the terminal.&lt;br&gt;
Vising the URL, you'll see the GraphiQL interface as shown in the image below:&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%2F3dw498pq5aoa2nkqo0tz.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%2F3dw498pq5aoa2nkqo0tz.PNG" alt="GraphiQL interface" width="800" height="151"&gt;&lt;/a&gt;&lt;br&gt;
To test the API, execute the following query in the provided GraphQL interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  detectProfanity(paragraph: "Don't be an asshole", character: "%"){
    paragraph
    isProfane
    censoredParagraph
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you'll get the following response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "data": {
    "detectProfanity": {
      "paragraph": "Don't be an asshole",
      "isProfane": true,
      "censoredParagraph": "Don't be an %%%%"
    }
  }
}
&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%2Fu9wurqk9f5o5fp7elanq.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%2Fu9wurqk9f5o5fp7elanq.PNG" alt="Executed Query" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Building a safe social network for all users is an important factor to consider when building social applications. In this tutorial, we introduced the concept of profanity and how to detect and filter profane words in an application. We used the Python framework Flask and GraphQL to illustrate how to build a profanity filter API.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>api</category>
      <category>howto</category>
    </item>
    <item>
      <title>How to build a Cron Job using Node.js</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Tue, 12 Nov 2024 10:13:55 +0000</pubDate>
      <link>https://dev.to/frankdev20/how-to-build-a-cron-job-using-nodejs-3eck</link>
      <guid>https://dev.to/frankdev20/how-to-build-a-cron-job-using-nodejs-3eck</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
A cron job is a time-based task scheduler commonly used to automate repetitive tasks on a Linux or Unix-based system. These tasks can include running backups, updating databases, generating reports, and sending email reminders. In this article, we will discuss how to build a cron job using Node.js, a popular JavaScript runtime environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; is an open-source, cross-platform JavaScript runtime that allows developers to build server-side applications using JavaScript. One of the key benefits of using Node.js for building cron jobs is that it can handle multiple concurrent connections and is highly scalable. This article will cover the basics of creating a cron job using Node.js, including setting up a schedule and running the job. We will also discuss some best practices for building and deploying cron jobs in a production environment. By the end of this article, you will have the knowledge and tools needed to build your cron job with Node.js.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is a cron job?
&lt;/h3&gt;

&lt;p&gt;A cron job is a scheduled task that runs automatically on a Unix-based operating system. The name "cron" is derived from the Greek word "Chronos" meaning time. Cron jobs automate repetitive tasks, such as sending emails, updating databases, and generating reports. They are typically run on a schedule, such as every day at a specific time or every hour.&lt;br&gt;
Cron jobs are defined using a simple text file called the crontab which contains a list of commands to be executed and the schedule on which to execute them. Each line in the crontab represents a single cron job and follows a specific format. The format includes six fields, separated by spaces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The minute (0-59).&lt;/li&gt;
&lt;li&gt;The hour (0-23).&lt;/li&gt;
&lt;li&gt;The day of the month (1-31).&lt;/li&gt;
&lt;li&gt;The month (1-12).&lt;/li&gt;
&lt;li&gt;The day of the week (0-7, where both 0 and 7 represent Sunday).&lt;/li&gt;
&lt;li&gt;The command to be executed.&lt;/li&gt;
&lt;/ol&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%2Fzcgiyl2qdymimi6wofbm.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%2Fzcgiyl2qdymimi6wofbm.PNG" alt="Cron job table" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the code snippet below illustrates how to schedule a task to run at every minute interval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cron = require("node-cron");

  cron.schedule("* * * * *", () =&amp;gt; {
    console.log("running a task at every minute");
 })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first argument passed into the cron.schedule function is the cron expression. You use the expression to specify the time at which the job should run. &lt;br&gt;
The asterisks used in the code above represent a different unit of time and are part of the crontab syntax. Using five asterisks (* * * * &lt;em&gt;) is the default crontab for scheduling a task every five minutes. A cron job can also be configured to run at more specific intervals using special characters such as asterisks (&lt;/em&gt;) and commas (,). For example, an asterisk (*) in the hour field would run the command every hour, while a comma-separated list of values (e.g. "1,15,30") would run the command at the specified minutes.&lt;br&gt;
The second argument is the function (job) that gets executed when the expression in the first argument is passed. &lt;br&gt;
The node-cron schedule function has an optional third argument, the configuration object used for job scheduling. The code below illustrates what the optional argument should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    schedule: true,
    timezone: "Asia/Kolkata"

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

&lt;/div&gt;



&lt;p&gt;The schedule by default is true, if you decide to set it to false, you’ll have to invoke the start method on the job object. A Job object is returned whenever the schedule function is called. &lt;br&gt;
To execute the cron job, 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;cron.start();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron jobs are a powerful tool for automating tasks on a server, but they must be used with caution. Incorrectly configured cron jobs can cause system crashes or data loss. It's important to test cron jobs thoroughly before deploying them in a production environment and to &lt;a href="https://www.airplane.dev/blog/how-to-monitor-cron-jobs" rel="noopener noreferrer"&gt;monitor their performance regularly&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of using Node.js for cron jobs
&lt;/h3&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%2Fzvy6z8huihml1ne7uqhc.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%2Fzvy6z8huihml1ne7uqhc.PNG" alt="Logo of Nodejs" width="800" height="422"&gt;&lt;/a&gt;&lt;br&gt;
Node.js is a powerful JavaScript runtime built on Chrome's V8 JavaScript engine, which has become increasingly popular in recent years for building web applications and other types of server-side software. One of the most significant benefits of using Node.js for cron jobs is that it allows developers to use JavaScript on the server-side, making it easy to integrate with other technologies and services.&lt;/p&gt;

&lt;p&gt;Another advantage of using Node.js for cron jobs is its ability to handle asynchronous code execution. Node.js is designed to handle multiple requests asynchronously, which is essential for running cron jobs that may take a long time to complete. With its event-driven architecture, Node.js can handle these tasks without blocking the execution of other code, ensuring that the server remains responsive and can handle other requests.&lt;/p&gt;

&lt;p&gt;One other benefit of using Node.js for cron jobs is its vast ecosystem of packages and modules. Node.js has an extensive library of modules that can be easily integrated into a cron job, such as the popular node-cron package, which provides a simple and flexible way to schedule tasks. This allows developers to quickly and easily build cron jobs without having to start from scratch.&lt;/p&gt;

&lt;p&gt;Furthermore, Node.js has become a popular choice for building web applications and APIs, and it's easy to use the same codebase for both the web application and the cron job. This makes it easy to share data between the cron job and the web application and to leverage existing code and libraries.&lt;/p&gt;

&lt;p&gt;Additionally, Node.js has a large and active community, which means that it's easy to find support and resources for building cron jobs. There are many tutorials, articles, and forums available that can help developers get started with building cron jobs using Node.js.&lt;/p&gt;

&lt;p&gt;Furthermore, Node.js is an excellent choice for building cron jobs, due to its ability to handle asynchronous code execution, a vast ecosystem of modules and packages, easy integration with web applications, and large community support. With Node.js, developers can quickly and easily build powerful and reliable cron jobs that can automate repetitive tasks, such as sending emails, updating databases, and generating reports.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up a cron job
&lt;/h3&gt;

&lt;p&gt;The Node.js &lt;a href="https://www.npmjs.com/package/package" rel="noopener noreferrer"&gt;Package Manager&lt;/a&gt; (NPM) registry contains a very useful cron job package, &lt;a href="https://www.npmjs.com/package/node-cron" rel="noopener noreferrer"&gt;node-cron&lt;/a&gt; that can be used to schedule jobs that run at specific intervals. &lt;/p&gt;

&lt;p&gt;I assume you already know how to install and set up a Node.js development environment, click on the &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/development_environment" rel="noopener noreferrer"&gt;link to install and set up your Node.js development environment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get started with this tutorial, create a new project folder by running this command on your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir project-folder-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On your terminal change the directory into the just created folder using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd project-folder-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the root of the folder run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above creates amongst other files &lt;code&gt;package.json&lt;/code&gt; file, which is useful for tracking your project dependencies and other necessary information.&lt;/p&gt;

&lt;p&gt;Now install the &lt;code&gt;node-cron&lt;/code&gt; package by running the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i node-cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the node-cron module in the project. Check your &lt;code&gt;package.json&lt;/code&gt; file to confirm this is installed.&lt;/p&gt;

&lt;p&gt;Next, build a Node.js server and use node-cron to schedule a task. Let’s say for this example, we want to print to the console this statement “Zenrow technical writer” every minute.&lt;br&gt;
Create a new file, &lt;strong&gt;cronapp.js&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In your new &lt;strong&gt;cronapp.js&lt;/strong&gt; file, require the node-cron module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cron = require("node-cron")

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

&lt;/div&gt;



&lt;p&gt;Add the following lines of code to your &lt;strong&gt;cronapp.js&lt;/strong&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;cron.schedule("* * * * *", () =&amp;gt; {
    console.log("Zenrow technical writer");
 })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the script, you're in the correct directory and run the command below:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The expected output should look like seen below:&lt;/p&gt;

&lt;p&gt;Zenrow technical writer&lt;br&gt;
Zenrow technical writer&lt;br&gt;
Zenrow technical writer&lt;br&gt;
Zenrow technical writer&lt;br&gt;
Zenrow technical writer&lt;br&gt;
Zenrow technical writer&lt;br&gt;
….&lt;br&gt;
The output above represents what was printed to the console every minute before the server was stopped using &lt;code&gt;CTRL C&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Sending Emails
&lt;/h3&gt;

&lt;p&gt;Sending periodic emails is an important area of application for cron job. In this section, we will use the Node.js nodemailer and node-cron module to implement sending periodic email functionality. Nodemailer module support test account created with &lt;a href="https://ethereal.email/" rel="noopener noreferrer"&gt;Ethereal Email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To create a test account using Ethereal, click on the &lt;strong&gt;Create Ethereal Account&lt;/strong&gt; button.&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%2F2b5kxis085ut0dz1thx9.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%2F2b5kxis085ut0dz1thx9.PNG" alt="Ethereal test email" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A test email will be generated automatically for you.&lt;/p&gt;

&lt;p&gt;On your terminal, change the directory to the root folder using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd project-folder-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install nodemailer module using the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i nodemailer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new file, &lt;strong&gt;cronEmail.js&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then require the necessary packages as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cron = require("node-cron");
const nodemailer = require("nodemailer")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",
    port: 587,


    auth: {
        user: "demoemail@ethereal.email",
        pass: "demopassword"
    }
 })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above creates a mail transporter and sets the user and password.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt;: In live projects, you would use environment variables to store your password and any other sensitive information.&lt;/p&gt;

&lt;p&gt;Next, add the following code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron.schedule("24 13 * * *", () =&amp;gt; {
    console.log("test")


    let data = {
        from: "demoemail@ethereal.email",
        to: "demoemail@ethereal.email",
        subject: "Test Email",
        text: "This is a test email"
    };


    transporter.sendMail(data, (err, info) =&amp;gt;{
        if(err){
            throw err;
        }else{
            console.log("Email sent");
        }
    })
 })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code snippet above schedules an email to be sent at (“24 13 * * *”), the pattern means emails will be sent at 1:24 pm every day. The code will send an email with the following contents: &lt;/p&gt;

&lt;p&gt;Subject: “Test Email”&lt;br&gt;
Text: “This is a test email” &lt;/p&gt;

&lt;p&gt;To the Ethereal test email account.&lt;/p&gt;

&lt;p&gt;We have covered how to use a cron job to schedule tasks, and send emails, the following section will illustrate how to delete files with a cron job. This use case becomes handy when you want to routinely delete a specific log file from the server at a particular interval.&lt;/p&gt;

&lt;p&gt;Create a log file named:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;zenrow.log&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then, add a message:&lt;/p&gt;

&lt;p&gt;Zenrow logs&lt;/p&gt;

&lt;p&gt;Create a new &lt;strong&gt;cronDelete.js&lt;/strong&gt; file&lt;/p&gt;

&lt;p&gt;Inside the &lt;strong&gt;cronDelete.js&lt;/strong&gt; file, require the &lt;strong&gt;fileSystem (fs)&lt;/strong&gt; and &lt;strong&gt;node-cron&lt;/strong&gt; modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const cron = require("node-cron")
const fs = require("fs")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code snippets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cron.schedule("48 14 * * *", function() {
    console.log("Running Cron Job");
    fs.unlink("Zenrow.log", (err) =&amp;gt; {
      if (err) throw err;
      console.log('Log file successfully deleted');
    });
  });

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

&lt;/div&gt;



&lt;p&gt;The schedule pattern: “48 14 * * *”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a value for minutes and hours as 48 and 14 respectively&lt;/li&gt;
&lt;li&gt;It didn’t define the day&lt;/li&gt;
&lt;li&gt;It didn’t define the day of the week or Month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: You can set the cron pattern to suit your needs.&lt;/p&gt;

&lt;p&gt;Run the script with 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;node cronDelete.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script will execute at 14:48PM and the zenrow.log will be deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some best practices for building and deploying a cron job
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Test cron jobs thoroughly before deploying them to a production environment. This will help ensure that they are working as expected and won't cause any issues when they are in live environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a centralized logging system to collect and aggregate log data from cron jobs. This will make it easier to monitor and troubleshoot any issues that arise. You can learn more about how to use airplane to monitor cron jobs here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep your cron jobs as simple as possible and do not include any unnecessary logic or functionality. This will help ensure that they are easy to maintain and debug&lt;br&gt;
.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a notification system to alert you when a cron job fails or when it completes successfully.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a monitoring tool to keep an eye on the performance and uptime of your cron jobs. This will help you quickly identify and resolve any issues that may arise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a scheduler like &lt;a href="https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/" rel="noopener noreferrer"&gt;Kubernetes cronjob&lt;/a&gt; to schedule the cron job, rather than using the built-in &lt;a href="http://www.linfo.org/daemon.html" rel="noopener noreferrer"&gt;cron daemon&lt;/a&gt;. This allows for better scalability and fault tolerance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common issues with cron jobs
&lt;/h3&gt;

&lt;p&gt;Cron jobs are a powerful tool for automating tasks on a server, but they can also come with some challenges and potential issues. Some of the most common issues with cron jobs include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lack of monitoring&lt;/strong&gt;: If there is no proper monitoring, it may be difficult to detect when a cron job has failed or is not running correctly. This can lead to data inconsistencies or delays in completing important tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cron job failure&lt;/strong&gt;: Cron jobs can fail for various reasons such as network issues, missing dependencies, or bugs in the code. It is important to have proper error handling and logging in place to detect and diagnose any issues that may arise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Overlapping cron jobs&lt;/strong&gt;: Another common issue is that multiple cron jobs scheduled to run at the same time can lead to resource conflicts and potential data inconsistencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security risks&lt;/strong&gt;: Cron jobs can also pose security risks if not properly configured. For example, a cron job that runs with root permissions can be exploited by malicious actors to gain access to sensitive information.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strategies for troubleshooting cron jobs
&lt;/h3&gt;

&lt;p&gt;Troubleshooting cron jobs can be a challenging task, but with the right strategies, you can quickly identify and resolve any issues. Here are some strategies for troubleshooting cron jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check the cron log&lt;/strong&gt;: Most systems have a log that records when cron jobs run and any errors that occur. The location of the log file varies depending on the operating system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check the cron schedule&lt;/strong&gt;: Make sure that the cron schedule is correct and that the job is scheduled to run at the correct time.&lt;br&gt;
Check the environment variables: Some cron jobs rely on environment variables, so make sure that the necessary environment variables are set and that they have the correct values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Before deploying to production, test the script manually&lt;/strong&gt;: Try running the script manually from the command line to see if it runs correctly and to check for any errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check for dependencies&lt;/strong&gt;: Ensure that all necessary dependencies for the command or script specified in the cron job are installed and configured correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schedule the job with different intervals&lt;/strong&gt;: Try scheduling it with different intervals, like every minute or every 5 minutes, to check if it works fine.&lt;br&gt;
By following these strategies, you can quickly identify and fix problems with your cron jobs, ensuring that they run smoothly and efficiently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Building a cron job with Node.js is a simple and efficient way to automate repetitive tasks in your applications. By using the node-cron package, you can easily schedule tasks to run at specified intervals, making it easy to keep your applications running smoothly. This article has provided a step-by-step guide for building a cron job with Node.js, including code snippets, and also discussed the strategies for troubleshooting cron jobs. With this knowledge, you can now easily automate tasks in your Node.js applications, freeing up valuable time and resources.&lt;/p&gt;

</description>
      <category>cronjob</category>
      <category>scheduler</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using Node.js for Ecommerce Platforms</title>
      <dc:creator>Frank Joseph</dc:creator>
      <pubDate>Mon, 30 Jan 2023 09:06:28 +0000</pubDate>
      <link>https://dev.to/frankdev20/using-nodejs-for-ecommerce-platforms-727</link>
      <guid>https://dev.to/frankdev20/using-nodejs-for-ecommerce-platforms-727</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; is a JavaScript server-side runtime development environment. It is built on Chrome’s V8 engine; it gives software developers the ability to write JavaScript code that runs on the server side. It is an open source and cross-platform server-side environment developer by Ryan Dahl.&lt;/p&gt;

&lt;p&gt;Node.js is a powerful, open-source, JavaScript-based platform that allows developers to build high-performance and scalable network applications. One of the areas where Node.js shines is ecommerce development. With Node.js, developers can build fast, efficient, and dynamic ecommerce applications that can handle a large number of products, customers, and transactions. However, as with any technology, there are pros and cons to using Node.js for ecommerce.&lt;/p&gt;

&lt;p&gt;In this guide, we will explore in-depth the pros and cons of using Node.js for e-commerce. Whether you're an experienced ecommerce developer or just getting started, this guide will provide you with the knowledge you need to decide if Node.js is suitable for your next e-commerce store development.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Node.js?
&lt;/h1&gt;

&lt;h3&gt;
  
  
  How Node.js Works
&lt;/h3&gt;

&lt;p&gt;Node.js applications are asynchronous and non-blocking by default. In this architecture, a single thread is used to manage multiple requests. Its event-driven nature contributes to its speed when used to develop applications. Some of Node.js features are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-Threaded Architecture&lt;/strong&gt;: Node.js uses single-threaded architecture and event looping. When compared with other server-side languages that create limited threads to handle requests, Node.js is considered more scalable as it handles requests in a non-blocking manner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous by Design&lt;/strong&gt;: Node.js APIs are asynchronous by design, meaning Node.js handles requests asynchronously. It doesn't have to wait for one request to finish before it moves on to the next one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node Package Manager (NPM)&lt;/strong&gt;: It is an online registry for Node.js packages. This repository contains more than a million different packages useful for the development of Node.js applications. Anyone can access this registry and install packages for their application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event-Driven Mechanism&lt;/strong&gt;: This unique feature of Node.js helps make the program as simple as possible and synchronizes the occurrence of multiple events. The event-driven mechanism consists of a callback function and an event loop.&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%2Fd9hj3qrt3rdyi8je4ka4.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%2Fd9hj3qrt3rdyi8je4ka4.png" alt="Node.js Logo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges in Ecommerce
&lt;/h2&gt;

&lt;p&gt;In recent years, ecommerce has gained a lot of attention and competition is becoming intense. One metric that would contribute to success is the nature of responsiveness of your applications. This is a clear reason why it is important to choose tools that would give users a better experience.&lt;/p&gt;

&lt;p&gt;Some challenges encountered within the ecommerce sectors are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Technology:&lt;/p&gt;

&lt;p&gt;Technology changes fast, so developers have constantly stay inform with the changes. Some programming languages do not entirely support both frontend and backend development, and so there is a need to use different languages for the frontend and backend.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Programming language barrier&lt;/p&gt;

&lt;p&gt;Some programming languages are heavy and contribute to lagging in an ecommerce application or increased application load time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Expert Experience:&lt;/p&gt;

&lt;p&gt;Building any complex application requires expert skills and domain knowledge&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pros of using Node.js for Ecommerce
&lt;/h2&gt;

&lt;p&gt;Using Node.js to build an ecommerce store helps to solve some challenges with ecommerce listed above. Node.js helps developers to build high-performance data-streaming web applications. The points shows node.js ecommerce pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Node.js is Open Source:&lt;/p&gt;

&lt;p&gt;As an open source software, Node.js enjoys community support. When building an ecommerce web app with Node.js, you’re sure to get support from the community (other experienced developers) whenever you run into challenges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fast Development:&lt;/p&gt;

&lt;p&gt;One key Node.js advantage is that development is fast. This is due to the ability to reuse existing open-source tools within your application which saves time on features without having to reinvent the wheel. Several Node.js packages can be downloaded and reused to build an online store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cross-Platform Compatibility:&lt;/p&gt;

&lt;p&gt;Node.js is a cross-platform development environment and can be used to build websites and applications that can work on different types of computers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Full-stack development:&lt;/p&gt;

&lt;p&gt;Building an ecommerce application with Node.js means that you can use a single programming language to build both the backend and frontend. Developers need only to know JavaScript to be able to build an online store with Node.js.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability and Performance: The non-blocking and event-driven feature of Node.js make it a scalable option for building ecommerce. It directly interacts with the V8 JavaScript engine, enabling easy compilation into machine codes with high efficiency and speed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cons of Using Node.js for Ecommerce
&lt;/h2&gt;

&lt;p&gt;As with most programming languages, Node.js has garnered a lot of support and huge acceptance, maybe because of the growing JavaScript ecosystem. Even with its huge community support, Node.js has some drawbacks, and they affect how it is used in ecommerce applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Poor Dependency Management:&lt;/p&gt;

&lt;p&gt;Numerous tools in the npm registry are either of poor quality or need to be fully documented/tested. Additionally, the registry's design doesn't allow it to present the tools according to their ranking or quality. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Asynchronous programming can be difficult:&lt;/p&gt;

&lt;p&gt;Node.js uses an asynchronous programming model, which can make it more challenging for developers to write and maintain code, especially if they are not familiar with this programming paradigm.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High server resource usage: Node.js is known to use more server resources than other server-side technologies, which can be a concern for ecommerce websites that need to handle a large number of requests and transactions. This can increase hosting costs and make it more difficult to achieve optimal performance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Medusa, a Node.js Ecommerce Platform
&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%2Fiuk5rg97gqdfdk5m79zw.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%2Fiuk5rg97gqdfdk5m79zw.PNG" alt="Medusa Admin panel" width="800" height="325"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/medusajs/medusa" rel="noopener noreferrer"&gt;Medusa&lt;/a&gt; is an open source composable commerce engine that’s a viable option for developing ecommerce web applications. One significant distinctive attribute of Medusa is that it offers merchants limitless customization opportunities.&lt;/p&gt;

&lt;p&gt;Medusa is divided into three components, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Medusa server: This section is the backend that exposes REST APIs.&lt;/li&gt;
&lt;li&gt;Admin Panel: This is where developers or platform users can control and configure their store's features.&lt;/li&gt;
&lt;li&gt;Frontend: This section can be created using any framework, Nextjs, or static site generators like Gatsby. This is the frontend or user-facing side of the Medusa platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advantages of Using Medusa
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Customization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Medusa is built with customization in mind. Although it provides customizable &lt;a href="https://docs.medusajs.com/introduction/#features" rel="noopener noreferrer"&gt;features out-of-the-box&lt;/a&gt;, this is possible because of the composable open-source architecture. &lt;/p&gt;

&lt;p&gt;Medusa includes more customization options for merchants. For example, developers can customize the data layer or backend of a Medusa store based on business preference to better serve their customers. For example, users can integrate with Strapi, Contentful, or any other CMSes. &lt;/p&gt;

&lt;p&gt;Plugins allow developers to implement custom features or integrate third-party services like payment into medusa. You can create or use existing plugins to integrate your store with third-party services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medusa is open source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means that almost anyone can access its source code. It enjoys a huge developer community support and collaboration, this is very important as the software is tested by a community of developers. Ownership of codebase and customization ability. Medusa is complete free to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance and Scalability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Medusa is built to scale to meet customers’ needs. Any E-commerce store built with Medusa is maintainable and scalable. Medusa is lightweight to maintain, easy to use and it is capable of scaling with business growth to meet customers’ needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Site performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Medusa’s headless architecture decouples the frontend from the backend. The importance of this kind of architecture is that the site loads faster since it loads independently of the backend and only interacts with it to retrieve or add data.&lt;/p&gt;

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

&lt;p&gt;Node.js is a great tool for developing high-performing web applications. This guide explicitly highlighted what Node.js is, how it works, ecommerce, and the pros and cons of using Node.js for ecommerce applications. To get started with Medusa e-commerce engine, &lt;a href="https://docs.medusajs.com/quickstart/quick-start/" rel="noopener noreferrer"&gt;click here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
