<?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: Sergio Alberto Colque Ponce</title>
    <description>The latest articles on DEV Community by Sergio Alberto Colque Ponce (@sergiocolqueponce).</description>
    <link>https://dev.to/sergiocolqueponce</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%2F3069513%2Fc20383b8-e246-4473-8ad5-0c76ac9cf7c0.png</url>
      <title>DEV Community: Sergio Alberto Colque Ponce</title>
      <link>https://dev.to/sergiocolqueponce</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sergiocolqueponce"/>
    <language>en</language>
    <item>
      <title>🗄️ Build an MCP Server for PostgreSQL: Query Your Database with Claude &amp; Any AI ClientHappy building</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Wed, 03 Dec 2025 04:37:07 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/build-an-mcp-server-for-postgresql-query-your-database-with-claude-any-ai-client-5m6</link>
      <guid>https://dev.to/sergiocolqueponce/build-an-mcp-server-for-postgresql-query-your-database-with-claude-any-ai-client-5m6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The Model Context Protocol (MCP) is transforming how AI applications interact with data sources. While weather servers and basic examples are great starting points, most real-world applications need database access. In this guide, we'll build a production-ready MCP Server for PostgreSQL that allows Claude, VSCode, and any MCP client to query your database safely and efficiently.&lt;/p&gt;

&lt;p&gt;By the end, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully functional PostgreSQL MCP Server&lt;/li&gt;
&lt;li&gt;Examples for SELECT queries, aggregations, and data retrieval&lt;/li&gt;
&lt;li&gt;A public repository you can deploy to production&lt;/li&gt;
&lt;li&gt;Integration with Claude Desktop and other MCP clients&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Problem Does This Solve?
&lt;/h2&gt;

&lt;p&gt;Imagine asking Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What are our top 10 customers by revenue this quarter?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Show me all incidents from the past 7 days with critical severity."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a PostgreSQL MCP Server, Claude can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect directly to your database&lt;/li&gt;
&lt;li&gt;Execute safe, predefined queries&lt;/li&gt;
&lt;li&gt;Return structured data&lt;/li&gt;
&lt;li&gt;Help you analyze and act on that data in real-time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BI dashboards powered by AI&lt;/li&gt;
&lt;li&gt;Incident management automation&lt;/li&gt;
&lt;li&gt;Customer data analysis&lt;/li&gt;
&lt;li&gt;Real-time reporting and insights&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Our MCP Server acts as a bridge between MCP clients (Claude, VSCode) and PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP Client (Claude)
       |
    MCP Protocol
       |
    MCP Server (TypeScript)
       |
  pg (PostgreSQL Driver)
       |
  PostgreSQL Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;TypeScript knowledge&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Basic MCP understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Project Setup
&lt;/h2&gt;

&lt;p&gt;Create the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;mcp-postgres-server
&lt;span class="nb"&gt;cd &lt;/span&gt;mcp-postgres-server
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; @modelcontextprotocol/sdk pg dotenv
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript @types/node @types/pg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Database Service
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;src/database.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;QueryResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DatabaseService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queryUsers&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT id, name, email FROM users LIMIT 10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queryIncidents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT id, title, status, severity FROM incidents WHERE status = $1 ORDER BY created_at DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getStats&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
      SELECT 
        COUNT(*) as total_incidents,
        SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count,
        SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open_count
      FROM incidents
    `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: MCP Server Implementation
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;src/server.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Server&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/server/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StdioServerTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/server/stdio.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CallToolRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ListToolsRequestSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/types.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DatabaseService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./database.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres-mcp-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DatabaseService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ListToolsRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retrieve recent users from the database&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_open_incidents&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get all open incidents sorted by date&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_database_stats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Get incident statistics (total, critical, open)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CallToolRequestSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_open_incidents&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;incidents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryIncidents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;incidents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_database_stats&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStats&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Unknown tool: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;isError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PostgreSQL MCP Server running on stdio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Configuration
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL=postgresql://user:password@localhost:5432/mydb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node dist/server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc &amp;amp;&amp;amp; npm start"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Running Locally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Connecting to Claude Desktop
&lt;/h2&gt;

&lt;p&gt;Edit your Claude Desktop config file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;: &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: &lt;code&gt;%APPDATA%\\Claude\\claude_desktop_config.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/absolute/path/to/dist/server.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://user:password@localhost:5432/mydb"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Desktop. You should now see a database icon and access to these tools!&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Production
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Connection Pooling&lt;/strong&gt;: Use pg Pool (already in code)&lt;br&gt;
✅ &lt;strong&gt;Parameterized Queries&lt;/strong&gt;: Always use $1, $2 placeholders&lt;br&gt;
✅ &lt;strong&gt;Error Handling&lt;/strong&gt;: Catch and return meaningful errors&lt;br&gt;
✅ &lt;strong&gt;Validation&lt;/strong&gt;: Validate tool inputs before querying&lt;br&gt;
✅ &lt;strong&gt;Monitoring&lt;/strong&gt;: Log all queries and response times&lt;br&gt;
✅ &lt;strong&gt;Rate Limiting&lt;/strong&gt;: Implement backpressure for high-volume requests&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Repository
&lt;/h2&gt;

&lt;p&gt;Complete source code available at:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/srg-cp/mcp-postgres-server" rel="noopener noreferrer"&gt;GitHub Repository: mcp-postgres-server&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Full TypeScript implementation&lt;/li&gt;
&lt;li&gt;Docker support&lt;/li&gt;
&lt;li&gt;Deployment scripts&lt;/li&gt;
&lt;li&gt;Example schema&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Options
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google Cloud Run
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud run deploy mcp-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-env-vars&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_connection_string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; mcp-postgres &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_url mcp-postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;You've built a production-ready PostgreSQL MCP Server! This bridge between AI models and databases opens up possibilities for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-powered dashboards&lt;/li&gt;
&lt;li&gt;Automated incident response&lt;/li&gt;
&lt;li&gt;Intelligent data analysis&lt;/li&gt;
&lt;li&gt;Real-time business intelligence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same patterns can extend to any database or data source. Happy building!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>llm</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Modern Data Visualization Tools: A Comprehensive Guide to Streamlit, Dash, and Bokeh</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Wed, 17 Sep 2025 01:02:57 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/modern-data-visualization-tools-a-comprehensive-guide-to-streamlit-dash-and-bokeh-7ed</link>
      <guid>https://dev.to/sergiocolqueponce/modern-data-visualization-tools-a-comprehensive-guide-to-streamlit-dash-and-bokeh-7ed</guid>
      <description>&lt;p&gt;Building interactive dashboards and reports has never been easier. Let's explore three powerful Python libraries that are revolutionizing data visualization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In today's data-driven world, the ability to create compelling, interactive visualizations is crucial for data scientists, analysts, and developers. While traditional plotting libraries like Matplotlib and Seaborn are excellent for static visualizations, modern applications demand interactive dashboards and real-time reporting capabilities.&lt;br&gt;
This article explores three powerful Python frameworks that excel at creating web-based data visualization applications: Streamlit, Dash, and Bokeh. Each tool has its unique strengths and use cases, and we'll build practical examples to demonstrate their capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streamlit: Simplicity Meets Power&lt;/strong&gt;&lt;br&gt;
Streamlit has gained massive popularity for its incredibly simple approach to building data apps. With just a few lines of Python code, you can create interactive web applications without any web development knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero HTML, CSS, or JavaScript required&lt;/li&gt;
&lt;li&gt;Automatic reactivity and caching&lt;/li&gt;
&lt;li&gt;Built-in widgets and components&lt;/li&gt;
&lt;li&gt;Easy deployment options&lt;/li&gt;
&lt;li&gt;Strong community and ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Streamlit Example: Sales Dashboard&lt;/strong&gt;&lt;br&gt;
Let's build a comprehensive sales dashboard that demonstrates Streamlit's capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import random

# Set page configuration
st.set_page_config(
    page_title="Sales Analytics Dashboard",
    page_icon="📊",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Generate sample data
@st.cache_data
def load_data():
    np.random.seed(42)
    dates = pd.date_range(start='2023-01-01', end='2024-12-31', freq='D')

    data = []
    for date in dates:
        for region in ['North America', 'Europe', 'Asia', 'South America']:
            for product in ['Product A', 'Product B', 'Product C', 'Product D']:
                sales = np.random.normal(1000, 200)
                profit_margin = np.random.uniform(0.1, 0.3)
                data.append({
                    'date': date,
                    'region': region,
                    'product': product,
                    'sales': max(0, sales),
                    'profit': max(0, sales * profit_margin),
                    'units_sold': max(1, int(sales / np.random.uniform(50, 150)))
                })

    return pd.DataFrame(data)

# Load data
df = load_data()

# Sidebar filters
st.sidebar.header("Dashboard Filters")

# Date range filter
date_range = st.sidebar.date_input(
    "Select Date Range",
    value=[df['date'].min().date(), df['date'].max().date()],
    min_value=df['date'].min().date(),
    max_value=df['date'].max().date()
)

# Region filter
regions = st.sidebar.multiselect(
    "Select Regions",
    options=df['region'].unique(),
    default=df['region'].unique()
)

# Product filter
products = st.sidebar.multiselect(
    "Select Products",
    options=df['product'].unique(),
    default=df['product'].unique()
)

# Filter data
filtered_df = df[
    (df['date'].dt.date &amp;gt;= date_range[0]) &amp;amp;
    (df['date'].dt.date &amp;lt;= date_range[1]) &amp;amp;
    (df['region'].isin(regions)) &amp;amp;
    (df['product'].isin(products))
]

# Main dashboard
st.title("🏢 Sales Analytics Dashboard")
st.markdown("---")

# Key metrics
col1, col2, col3, col4 = st.columns(4)

with col1:
    total_sales = filtered_df['sales'].sum()
    st.metric(
        label="Total Sales",
        value=f"${total_sales:,.0f}",
        delta=f"{total_sales/1000000:.1f}M"
    )

with col2:
    total_profit = filtered_df['profit'].sum()
    profit_margin = (total_profit / total_sales) * 100 if total_sales &amp;gt; 0 else 0
    st.metric(
        label="Total Profit",
        value=f"${total_profit:,.0f}",
        delta=f"{profit_margin:.1f}% margin"
    )

with col3:
    total_units = filtered_df['units_sold'].sum()
    st.metric(
        label="Units Sold",
        value=f"{total_units:,}",
        delta=f"{len(filtered_df['product'].unique())} products"
    )

with col4:
    avg_order_value = total_sales / total_units if total_units &amp;gt; 0 else 0
    st.metric(
        label="Avg Order Value",
        value=f"${avg_order_value:.2f}",
        delta="Per unit"
    )

st.markdown("---")

# Charts
col1, col2 = st.columns(2)

with col1:
    st.subheader("📈 Sales Trend Over Time")

    # Aggregate data by date
    daily_sales = filtered_df.groupby('date')['sales'].sum().reset_index()

    fig_trend = px.line(
        daily_sales, 
        x='date', 
        y='sales',
        title="Daily Sales Performance"
    )
    fig_trend.update_layout(
        xaxis_title="Date",
        yaxis_title="Sales ($)",
        hovermode='x unified'
    )
    st.plotly_chart(fig_trend, use_container_width=True)

with col2:
    st.subheader("🌍 Sales by Region")

    region_sales = filtered_df.groupby('region')['sales'].sum().reset_index()

    fig_pie = px.pie(
        region_sales,
        values='sales',
        names='region',
        title="Regional Sales Distribution"
    )
    st.plotly_chart(fig_pie, use_container_width=True)

# Product performance
st.subheader("📦 Product Performance Analysis")

product_metrics = filtered_df.groupby('product').agg({
    'sales': 'sum',
    'profit': 'sum',
    'units_sold': 'sum'
}).reset_index()

fig_bar = px.bar(
    product_metrics,
    x='product',
    y='sales',
    color='profit',
    title="Sales and Profit by Product"
)
fig_bar.update_layout(
    xaxis_title="Product",
    yaxis_title="Sales ($)",
    coloraxis_colorbar_title="Profit ($)"
)
st.plotly_chart(fig_bar, use_container_width=True)

# Detailed data table
st.subheader("📊 Detailed Data View")

if st.checkbox("Show raw data"):
    st.dataframe(
        filtered_df.head(1000),
        use_container_width=True
    )

# Export functionality
st.markdown("---")
st.subheader("💾 Export Data")

csv = filtered_df.to_csv(index=False)
st.download_button(
    label="Download filtered data as CSV",
    data=csv,
    file_name=f"sales_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
    mime="text/csv"
)

# Footer
st.markdown("---")
st.markdown(
    """
    &amp;lt;div style='text-align: center'&amp;gt;
        &amp;lt;p&amp;gt;Built with ❤️ using Streamlit | Data refreshed daily&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
    """,
    unsafe_allow_html=True
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirements.txt for Streamlit:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;streamlit==1.28.0
pandas==2.0.3
numpy==1.24.3
plotly==5.15.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dash: The Enterprise Solution&lt;/strong&gt;&lt;br&gt;
Plotly Dash is perfect for building production-ready analytical web applications. It provides more control over layout and styling compared to Streamlit, making it ideal for enterprise dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highly customizable with HTML/CSS components&lt;/li&gt;
&lt;li&gt;Excellent callback system for interactivity&lt;/li&gt;
&lt;li&gt;Built on React.js for robust performance&lt;/li&gt;
&lt;li&gt;Extensive theming and styling options&lt;/li&gt;
&lt;li&gt;Strong integration with Plotly charts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dash Example: Financial Portfolio Tracker&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import dash
from dash import dcc, html, Input, Output, dash_table
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import yfinance as yf

# Initialize the Dash app
app = dash.Dash(__name__)
app.title = "Portfolio Analytics Dashboard"

# Sample portfolio data
PORTFOLIO = {
    'AAPL': {'shares': 100, 'purchase_price': 150},
    'GOOGL': {'shares': 50, 'purchase_price': 2500},
    'MSFT': {'shares': 75, 'purchase_price': 300},
    'AMZN': {'shares': 25, 'purchase_price': 3200},
    'TSLA': {'shares': 30, 'purchase_price': 800}
}

# Fetch real stock data
@app.callback(
    Output('portfolio-content', 'children'),
    Input('refresh-button', 'n_clicks')
)
def update_portfolio(n_clicks):
    # Get current stock prices
    symbols = list(PORTFOLIO.keys())
    stock_data = yf.download(symbols, period="1y", group_by='ticker')

    # Calculate portfolio metrics
    portfolio_value = 0
    portfolio_data = []

    for symbol, details in PORTFOLIO.items():
        try:
            current_price = stock_data[symbol]['Close'].iloc[-1]
            shares = details['shares']
            purchase_price = details['purchase_price']

            current_value = current_price * shares
            purchase_value = purchase_price * shares
            gain_loss = current_value - purchase_value
            gain_loss_pct = (gain_loss / purchase_value) * 100

            portfolio_value += current_value

            portfolio_data.append({
                'Symbol': symbol,
                'Shares': shares,
                'Purchase Price': f"${purchase_price:.2f}",
                'Current Price': f"${current_price:.2f}",
                'Current Value': f"${current_value:.2f}",
                'Gain/Loss': f"${gain_loss:.2f}",
                'Gain/Loss %': f"{gain_loss_pct:.2f}%"
            })
        except:
            # Handle data fetch errors
            portfolio_data.append({
                'Symbol': symbol,
                'Shares': details['shares'],
                'Purchase Price': f"${details['purchase_price']:.2f}",
                'Current Price': "N/A",
                'Current Value': "N/A",
                'Gain/Loss': "N/A",
                'Gain/Loss %': "N/A"
            })

    # Create portfolio performance chart
    symbols_with_data = [s for s in symbols if s in stock_data.columns.levels[0]]

    fig_performance = go.Figure()

    for symbol in symbols_with_data:
        try:
            prices = stock_data[symbol]['Close']
            fig_performance.add_trace(go.Scatter(
                x=prices.index,
                y=prices.values,
                mode='lines',
                name=symbol,
                hovertemplate=f'&amp;lt;b&amp;gt;{symbol}&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;' +
                             'Date: %{x}&amp;lt;br&amp;gt;' +
                             'Price: $%{y:.2f}&amp;lt;extra&amp;gt;&amp;lt;/extra&amp;gt;'
            ))
        except:
            continue

    fig_performance.update_layout(
        title="Stock Price Performance (1 Year)",
        xaxis_title="Date",
        yaxis_title="Price ($)",
        hovermode='x unified',
        template="plotly_white"
    )

    # Portfolio allocation pie chart
    allocation_data = []
    for symbol, details in PORTFOLIO.items():
        try:
            current_price = stock_data[symbol]['Close'].iloc[-1]
            value = current_price * details['shares']
            allocation_data.append({'Symbol': symbol, 'Value': value})
        except:
            continue

    if allocation_data:
        allocation_df = pd.DataFrame(allocation_data)
        fig_allocation = px.pie(
            allocation_df,
            values='Value',
            names='Symbol',
            title="Portfolio Allocation"
        )
    else:
        fig_allocation = go.Figure()
        fig_allocation.add_annotation(text="No data available", 
                                    showarrow=False, 
                                    font=dict(size=20))

    return [
        # Portfolio summary cards
        html.Div([
            html.Div([
                html.H3(f"${portfolio_value:.2f}", className="metric-value"),
                html.P("Total Portfolio Value", className="metric-label")
            ], className="metric-card"),

            html.Div([
                html.H3(f"{len(PORTFOLIO)}", className="metric-value"),
                html.P("Holdings", className="metric-label")
            ], className="metric-card"),

            html.Div([
                html.H3("Active", className="metric-value"),
                html.P("Portfolio Status", className="metric-label")
            ], className="metric-card")
        ], className="metrics-container"),

        # Charts
        html.Div([
            html.Div([
                dcc.Graph(figure=fig_performance)
            ], className="chart-container"),

            html.Div([
                dcc.Graph(figure=fig_allocation)
            ], className="chart-container")
        ], className="charts-row"),

        # Portfolio table
        html.Div([
            html.H3("Portfolio Holdings"),
            dash_table.DataTable(
                data=portfolio_data,
                columns=[{"name": i, "id": i} for i in portfolio_data[0].keys() if portfolio_data],
                style_cell={'textAlign': 'left'},
                style_header={'backgroundColor': 'rgb(230, 230, 230)', 'fontWeight': 'bold'},
                style_data_conditional=[
                    {
                        'if': {
                            'filter_query': '{Gain/Loss %} contains -',
                            'column_id': 'Gain/Loss %'
                        },
                        'backgroundColor': '#ffebee',
                        'color': 'red',
                    },
                    {
                        'if': {
                            'filter_query': '{Gain/Loss %} &amp;gt; 0',
                            'column_id': 'Gain/Loss %'
                        },
                        'backgroundColor': '#e8f5e8',
                        'color': 'green',
                    }
                ]
            )
        ], className="table-container")
    ]

# App layout
app.layout = html.Div([
    html.Div([
        html.H1("📈 Portfolio Analytics Dashboard", className="main-title"),
        html.Button("Refresh Data", id="refresh-button", className="refresh-btn")
    ], className="header"),

    html.Div(id="portfolio-content"),

    html.Footer([
        html.P("Built with Plotly Dash | Real-time market data via Yahoo Finance")
    ], className="footer")
])

# CSS styling
app.index_string = '''
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        {%metas%}
        &amp;lt;title&amp;gt;{%title%}&amp;lt;/title&amp;gt;
        {%favicon%}
        {%css%}
        &amp;lt;style&amp;gt;
            body {
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                margin: 0;
                padding: 0;
                background-color: #f5f5f5;
            }
            .header {
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 2rem;
                display: flex;
                justify-content: space-between;
                align-items: center;
                box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            }
            .main-title {
                margin: 0;
                font-size: 2.5rem;
                font-weight: 300;
            }
            .refresh-btn {
                background-color: white;
                color: #667eea;
                border: none;
                padding: 0.75rem 2rem;
                border-radius: 25px;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.3s ease;
            }
            .refresh-btn:hover {
                transform: translateY(-2px);
                box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            }
            .metrics-container {
                display: flex;
                justify-content: space-around;
                margin: 2rem;
                gap: 1rem;
            }
            .metric-card {
                background: white;
                padding: 2rem;
                border-radius: 15px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.1);
                text-align: center;
                flex: 1;
                transition: transform 0.3s ease;
            }
            .metric-card:hover {
                transform: translateY(-5px);
            }
            .metric-value {
                font-size: 2.5rem;
                font-weight: 700;
                margin: 0;
                color: #333;
            }
            .metric-label {
                color: #666;
                margin: 0.5rem 0 0 0;
                font-size: 1rem;
            }
            .charts-row {
                display: flex;
                margin: 2rem;
                gap: 1rem;
            }
            .chart-container {
                background: white;
                padding: 1rem;
                border-radius: 15px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.1);
                flex: 1;
            }
            .table-container {
                margin: 2rem;
                background: white;
                padding: 2rem;
                border-radius: 15px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            }
            .footer {
                background-color: #333;
                color: white;
                text-align: center;
                padding: 2rem;
                margin-top: 3rem;
            }
        &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        {%app_entry%}
        &amp;lt;footer&amp;gt;
            {%config%}
            {%scripts%}
            {%renderer%}
        &amp;lt;/footer&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
'''

if __name__ == '__main__':
    app.run_server(debug=True, host='0.0.0.0', port=8050)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirements.txt for Dash:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dash==2.14.0
plotly==5.15.0
pandas==2.0.3
numpy==1.24.3
yfinance==0.2.18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bokeh: Interactive Web Visualizations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bokeh excels at creating highly interactive, publication-ready visualizations for web browsers. It's particularly powerful for large datasets and complex interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-performance visualization of large datasets&lt;/li&gt;
&lt;li&gt;Flexible and powerful interaction capabilities&lt;/li&gt;
&lt;li&gt;Server applications for real-time data&lt;/li&gt;
&lt;li&gt;Beautiful, publication-ready output&lt;/li&gt;
&lt;li&gt;Extensive widget library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bokeh Example: Real-time Data Monitor&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from bokeh.plotting import figure, curdoc
from bokeh.layouts import column, row, layout
from bokeh.models import ColumnDataSource, Select, Slider, Button, Div
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.io import show
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import asyncio
from functools import partial

# Generate initial data
def generate_data(n_points=100):
    """Generate sample IoT sensor data"""
    np.random.seed(42)
    timestamps = pd.date_range(
        start=datetime.now() - timedelta(hours=1),
        periods=n_points,
        freq='30S'
    )

    # Simulate different sensor types
    sensors = {
        'temperature': {
            'values': 20 + 5 * np.sin(np.linspace(0, 4*np.pi, n_points)) + np.random.normal(0, 1, n_points),
            'unit': '°C',
            'color': '#ff6b6b'
        },
        'humidity': {
            'values': 50 + 20 * np.sin(np.linspace(0, 2*np.pi, n_points)) + np.random.normal(0, 3, n_points),
            'unit': '%',
            'color': '#4ecdc4'
        },
        'pressure': {
            'values': 1013 + 10 * np.sin(np.linspace(0, 6*np.pi, n_points)) + np.random.normal(0, 2, n_points),
            'unit': 'hPa',
            'color': '#45b7d1'
        },
        'air_quality': {
            'values': np.maximum(0, 100 + 50 * np.sin(np.linspace(0, 3*np.pi, n_points)) + np.random.normal(0, 10, n_points)),
            'unit': 'AQI',
            'color': '#96ceb4'
        }
    }

    return timestamps, sensors

# Initialize data
timestamps, sensors = generate_data()

# Create data sources
sources = {}
for sensor_name in sensors.keys():
    sources[sensor_name] = ColumnDataSource(data=dict(
        x=timestamps,
        y=sensors[sensor_name]['values']
    ))

# Create main plot
main_plot = figure(
    title="Real-time Sensor Monitoring Dashboard",
    x_axis_label="Time",
    y_axis_label="Sensor Values",
    x_axis_type="datetime",
    width=800,
    height=400,
    tools="pan,wheel_zoom,box_zoom,reset,save"
)

# Add lines for each sensor
lines = {}
for sensor_name, sensor_data in sensors.items():
    line = main_plot.line(
        'x', 'y',
        source=sources[sensor_name],
        legend_label=f"{sensor_name.title()} ({sensor_data['unit']})",
        line_color=sensor_data['color'],
        line_width=2,
        alpha=0.8
    )
    lines[sensor_name] = line

main_plot.legend.location = "top_left"
main_plot.legend.click_policy = "hide"

# Create individual sensor plots
sensor_plots = {}
for sensor_name, sensor_data in sensors.items():
    p = figure(
        title=f"{sensor_name.title()} Monitor",
        x_axis_label="Time",
        y_axis_label=f"{sensor_name.title()} ({sensor_data['unit']})",
        x_axis_type="datetime",
        width=350,
        height=250,
        tools="pan,wheel_zoom,box_zoom,reset"
    )

    p.line('x', 'y',
           source=sources[sensor_name],
           line_color=sensor_data['color'],
           line_width=3)

    p.circle('x', 'y',
             source=sources[sensor_name],
             color=sensor_data['color'],
             size=4)

    sensor_plots[sensor_name] = p

# Create controls
sensor_select = Select(
    title="Focus Sensor:",
    value="temperature",
    options=[(name, name.title()) for name in sensors.keys()]
)

update_interval = Slider(
    title="Update Interval (seconds):",
    value=2,
    start=1,
    end=10,
    step=1
)

start_button = Button(label="Start Monitoring", button_type="success")
stop_button = Button(label="Stop Monitoring", button_type="danger")

# Statistics display
stats_div = Div(text="&amp;lt;h3&amp;gt;Sensor Statistics&amp;lt;/h3&amp;gt;")

def update_stats():
    """Update statistics display"""
    stats_html = "&amp;lt;h3&amp;gt;📊 Live Sensor Statistics&amp;lt;/h3&amp;gt;&amp;lt;table style='width:100%; border-collapse: collapse;'&amp;gt;"
    stats_html += "&amp;lt;tr style='background-color: #f0f0f0;'&amp;gt;&amp;lt;th style='border: 1px solid #ddd; padding: 8px;'&amp;gt;Sensor&amp;lt;/th&amp;gt;&amp;lt;th style='border: 1px solid #ddd; padding: 8px;'&amp;gt;Current&amp;lt;/th&amp;gt;&amp;lt;th style='border: 1px solid #ddd; padding: 8px;'&amp;gt;Min&amp;lt;/th&amp;gt;&amp;lt;th style='border: 1px solid #ddd; padding: 8px;'&amp;gt;Max&amp;lt;/th&amp;gt;&amp;lt;th style='border: 1px solid #ddd; padding: 8px;'&amp;gt;Avg&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;"

    for sensor_name, sensor_data in sensors.items():
        current_data = sources[sensor_name].data['y']
        if len(current_data) &amp;gt; 0:
            current = current_data[-1]
            min_val = min(current_data)
            max_val = max(current_data)
            avg_val = sum(current_data) / len(current_data)
            unit = sensor_data['unit']

            stats_html += f"&amp;lt;tr&amp;gt;&amp;lt;td style='border: 1px solid #ddd; padding: 8px;'&amp;gt;{sensor_name.title()}&amp;lt;/td&amp;gt;"
            stats_html += f"&amp;lt;td style='border: 1px solid #ddd; padding: 8px;'&amp;gt;{current:.1f} {unit}&amp;lt;/td&amp;gt;"
            stats_html += f"&amp;lt;td style='border: 1px solid #ddd; padding: 8px;'&amp;gt;{min_val:.1f} {unit}&amp;lt;/td&amp;gt;"
            stats_html += f"&amp;lt;td style='border: 1px solid #ddd; padding: 8px;'&amp;gt;{max_val:.1f} {unit}&amp;lt;/td&amp;gt;"
            stats_html += f"&amp;lt;td style='border: 1px solid #ddd; padding: 8px;'&amp;gt;{avg_val:.1f} {unit}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;"

    stats_html += "&amp;lt;/table&amp;gt;"
    stats_div.text = stats_html

# Data update function
def update_data():
    """Simulate real-time data updates"""
    global timestamps, sensors

    # Generate new data point
    new_time = timestamps[-1] + timedelta(seconds=30)
    timestamps = timestamps.append(pd.Index([new_time]))

    for sensor_name, sensor_data in sensors.items():
        # Simulate sensor reading with some noise and trend
        last_value = sensor_data['values'][-1]
        noise = np.random.normal(0, 1)
        trend = np.sin(len(sensor_data['values']) * 0.1) * 0.5
        new_value = last_value + noise + trend

        sensor_data['values'] = np.append(sensor_data['values'], new_value)

        # Keep only last 100 points
        if len(sensor_data['values']) &amp;gt; 100:
            sensor_data['values'] = sensor_data['values'][-100:]
            timestamps = timestamps[-100:]

    # Update data sources
    for sensor_name in sensors.keys():
        sources[sensor_name].data = dict(
            x=timestamps,
            y=sensors[sensor_name]['values']
        )

    update_stats()

# Control callbacks
monitoring_active = False

def start_monitoring():
    global monitoring_active
    monitoring_active = True
    curdoc().add_periodic_callback(update_data, update_interval.value * 1000)

def stop_monitoring():
    global monitoring_active
    monitoring_active = False
    curdoc().remove_periodic_callback(update_data)

start_button.on_click(lambda: start_monitoring())
stop_button.on_click(lambda: stop_monitoring())

# Sensor selection callback
def update_focus(attr, old, new):
    """Update focus when sensor is selected"""
    focused_sensor = sensor_select.value
    # You could implement plot highlighting or other focus features here
    pass

sensor_select.on_change('value', update_focus)

# Initialize statistics
update_stats()

# Layout
controls = column(
    Div(text="&amp;lt;h2&amp;gt;🔧 Dashboard Controls&amp;lt;/h2&amp;gt;"),
    sensor_select,
    update_interval,
    row(start_button, stop_button),
    stats_div
)

sensor_grid = layout([
    [sensor_plots['temperature'], sensor_plots['humidity']],
    [sensor_plots['pressure'], sensor_plots['air_quality']]
])

# Main layout
dashboard_layout = layout([
    [Div(text="&amp;lt;h1&amp;gt;🌐 IoT Sensor Monitoring Dashboard&amp;lt;/h1&amp;gt;")],
    [main_plot],
    [controls, sensor_grid]
])

# Add to document
curdoc().add_root(dashboard_layout)
curdoc().title = "IoT Dashboard"

# Auto-start monitoring
start_monitoring()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requirements.txt for Bokeh:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bokeh==3.2.0
pandas==2.0.3
numpy==1.24.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deployment Guide&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streamlit Deployment (Streamlit Cloud)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push your code to GitHub&lt;/li&gt;
&lt;li&gt;Go to share.streamlit.io&lt;/li&gt;
&lt;li&gt;Connect your GitHub repo&lt;/li&gt;
&lt;li&gt;Deploy with one click!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dash Deployment (Heroku)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create Procfile
echo "web: gunicorn app:server" &amp;gt; Procfile

# Add to requirements.txt
echo "gunicorn==20.1.0" &amp;gt;&amp;gt; requirements.txt

# Deploy to Heroku
heroku create your-dash-app
git push heroku main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bokeh Deployment (Bokeh Server)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Run locally
bokeh serve dashboard.py --show

# Deploy to cloud server
bokeh serve dashboard.py --host 0.0.0.0 --port 5006 --allow-websocket-origin=*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;**Best Practices&lt;/p&gt;

&lt;p&gt;For All Tools:**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Optimize Data Loading: Use caching mechanisms&lt;/li&gt;
&lt;li&gt;Responsive Design: Ensure mobile compatibility&lt;/li&gt;
&lt;li&gt;Error Handling: Implement graceful failure modes&lt;/li&gt;
&lt;li&gt;User Experience: Provide loading indicators and feedback&lt;/li&gt;
&lt;li&gt;Security: Validate inputs and sanitize data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Performance Tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use data sampling for large datasets&lt;/li&gt;
&lt;li&gt;Implement lazy loading for heavy computations&lt;/li&gt;
&lt;li&gt;Cache frequently accessed data&lt;/li&gt;
&lt;li&gt;Use vectorized operations with NumPy/Pandas&lt;/li&gt;
&lt;li&gt;Optimize plot rendering by reducing data points when necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-World Applications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Streamlit Success Stories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Science Prototyping: Quickly validate ML models&lt;/li&gt;
&lt;li&gt;Business Intelligence: Executive dashboards for KPI monitoring&lt;/li&gt;
&lt;li&gt;Educational Tools: Interactive learning applications&lt;/li&gt;
&lt;li&gt;Research Presentations: Academic data visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dash in Production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Financial Trading Platforms: Real-time market analysis&lt;/li&gt;
&lt;li&gt;Manufacturing Dashboards: Production line monitoring&lt;/li&gt;
&lt;li&gt;Healthcare Analytics: Patient data visualization&lt;/li&gt;
&lt;li&gt;Supply Chain Management: Logistics optimization tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bokeh Applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scientific Computing: Large-scale data analysis&lt;/li&gt;
&lt;li&gt;Geospatial Analysis: Interactive mapping applications&lt;/li&gt;
&lt;li&gt;Network Visualization: Complex relationship mapping&lt;/li&gt;
&lt;li&gt;Time Series Analysis: High-frequency data monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code Repository&lt;br&gt;
All the examples from this article are available on GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone the repository
git clone https://github.com/yourusername/dataviz-tools-demo.git
cd dataviz-tools-demo

# Streamlit example
cd streamlit-dashboard
pip install -r requirements.txt
streamlit run app.py

# Dash example
cd ../dash-portfolio
pip install -r requirements.txt
python app.py

# Bokeh example
cd ../bokeh-monitor
pip install -r requirements.txt
bokeh serve dashboard.py --show
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Actions for Automated Deployment&lt;br&gt;
Here's a sample GitHub Actions workflow for automated deployment:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .github/workflows/deploy.yml
name: Deploy Dashboard

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  deploy-streamlit:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'

    - name: Install dependencies
      run: |
        pip install -r streamlit-dashboard/requirements.txt

    - name: Test Streamlit app
      run: |
        cd streamlit-dashboard
        streamlit run app.py --server.headless true &amp;amp;
        sleep 10
        curl -f http://localhost:8501 || exit 1

    - name: Deploy to Streamlit Cloud
      # Streamlit Cloud auto-deploys from GitHub
      run: echo "Deployed to Streamlit Cloud"

  deploy-dash:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Deploy to Heroku
      uses: akhileshns/heroku-deploy@v3.12.12
      with:
        heroku_api_key: ${{secrets.HEROKU_API_KEY}}
        heroku_app_name: "your-dash-app"
        heroku_email: "your-email@example.com"
        appdir: "dash-portfolio"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advanced Features and Extensions&lt;br&gt;
Streamlit Advanced Components:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Custom components example
import streamlit.components.v1 as components

# Embed custom HTML/JS
components.html("""
&amp;lt;div id="custom-widget"&amp;gt;
    &amp;lt;script&amp;gt;
        // Custom interactive widget
        function updateData() {
            // Your custom JavaScript here
        }
    &amp;lt;/script&amp;gt;
&amp;lt;/div&amp;gt;
""", height=400)

# File uploader with processing
uploaded_file = st.file_uploader("Choose a CSV file", type="csv")
if uploaded_file is not None:
    df = pd.read_csv(uploaded_file)
    st.dataframe(df)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dash Advanced Patterns:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Multi-page app structure
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc

# Navigation
navbar = dbc.NavbarSimple(
    children=[
        dbc.NavItem(dbc.NavLink("Dashboard", href="/dashboard")),
        dbc.NavItem(dbc.NavLink("Analytics", href="/analytics")),
        dbc.NavItem(dbc.NavLink("Settings", href="/settings")),
    ],
    brand="My Dashboard",
    brand_href="/",
    color="primary",
    dark=True,
)

# URL routing
@app.callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == '/dashboard':
        return dashboard_layout
    elif pathname == '/analytics':
        return analytics_layout
    else:
        return html.Div([
            html.H1('404: Not found', className='text-danger'),
            html.Hr(),
            html.P(f'The pathname {pathname} was not recognised...')
        ])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Bokeh Server Applications:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Advanced server app with WebSocket
from bokeh.server.server import Server
from tornado import gen

def modify_doc(doc):
    # Your Bokeh app logic here
    pass

# Custom server with additional routes
def create_server():
    server = Server({'/dashboard': modify_doc}, 
                   port=5006, 
                   allow_websocket_origin=["localhost:5006"])
    return server

if __name__ == '__main__':
    server = create_server()
    server.start()
    server.run_until_shutdown()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Testing and Quality Assurance&lt;br&gt;
Unit Testing for Dashboard Applications:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# test_dashboard.py
import pytest
import pandas as pd
from unittest.mock import patch
import streamlit as st

def test_data_processing():
    """Test data processing functions"""
    # Mock data
    test_data = pd.DataFrame({
        'date': pd.date_range('2023-01-01', periods=10),
        'value': range(10)
    })

    # Test your processing functions
    result = process_data(test_data)
    assert len(result) == 10
    assert 'processed_value' in result.columns

def test_streamlit_app():
    """Test Streamlit app components"""
    with patch('streamlit.write') as mock_write:
        # Test your app logic
        main_app()
        mock_write.assert_called()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Performance Testing:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# performance_test.py
import time
import pandas as pd
from memory_profiler import profile

@profile
def test_large_dataset_performance():
    """Test performance with large datasets"""
    # Generate large dataset
    large_df = pd.DataFrame({
        'x': range(100000),
        'y': range(100000)
    })

    start_time = time.time()

    # Test your processing
    result = your_processing_function(large_df)

    execution_time = time.time() - start_time

    # Assert performance requirements
    assert execution_time &amp;lt; 5.0  # Should complete in under 5 seconds
    assert len(result) &amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Security Considerations&lt;br&gt;
Data Protection:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Secure data handling
import os
from cryptography.fernet import Fernet

class SecureDataHandler:
    def __init__(self):
        # Use environment variables for keys
        key = os.environ.get('ENCRYPTION_KEY', Fernet.generate_key())
        self.cipher_suite = Fernet(key)

    def encrypt_data(self, data):
        return self.cipher_suite.encrypt(data.encode())

    def decrypt_data(self, encrypted_data):
        return self.cipher_suite.decrypt(encrypted_data).decode()

# Input validation
def validate_input(user_input):
    """Validate and sanitize user inputs"""
    if not isinstance(user_input, str):
        raise ValueError("Input must be string")

    # Remove potentially dangerous characters
    safe_input = re.sub(r'[&amp;lt;&amp;gt;"\']', '', user_input)
    return safe_input[:100]  # Limit length
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring and Analytics&lt;br&gt;
Application Monitoring:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# monitoring.py
import logging
import time
from functools import wraps

# Set up logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('dashboard.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def monitor_performance(func):
    """Decorator to monitor function performance"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        try:
            result = func(*args, **kwargs)
            execution_time = time.time() - start_time
            logger.info(f"{func.__name__} executed in {execution_time:.2f}s")
            return result
        except Exception as e:
            logger.error(f"Error in {func.__name__}: {str(e)}")
            raise
    return wrapper

# Usage tracking
class UsageTracker:
    def __init__(self):
        self.interactions = []

    def track_interaction(self, action, user_id=None):
        self.interactions.append({
            'timestamp': time.time(),
            'action': action,
            'user_id': user_id
        })

    def get_usage_stats(self):
        return {
            'total_interactions': len(self.interactions),
            'unique_actions': len(set(i['action'] for i in self.interactions))
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
The landscape of data visualization tools in Python offers something for every use case. Streamlit excels in rapid prototyping and simplicity, making it perfect for data scientists who want to quickly share their work. Dash provides the robustness and customization needed for production applications, while Bokeh offers unmatched flexibility for complex, interactive visualizations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/srg-cp/MindPlan" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tooling</category>
      <category>python</category>
      <category>datascience</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🚀 GitHub Actions as a Testing Management Tool: Real-world CI/CD with Python</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Thu, 03 Jul 2025 19:02:37 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/github-actions-as-a-testing-management-tool-real-world-cicd-with-python-4hbf</link>
      <guid>https://dev.to/sergiocolqueponce/github-actions-as-a-testing-management-tool-real-world-cicd-with-python-4hbf</guid>
      <description>&lt;h2&gt;
  
  
  🎯 Introduction
&lt;/h2&gt;

&lt;p&gt;In the world of modern software development, automated testing and continuous integration are no longer optional — they’re essential. That's where GitHub Actions shines: a powerful CI/CD tool built directly into GitHub.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore how GitHub Actions works as a Testing Management Tool, automate testing for a Python project, and demonstrate how to streamline code quality checks using real-world code examples.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ A complete public repo accompanies this article: &lt;a href="//github.com/srg-cp/gh-actions-python-tests"&gt;github.com/srg-cp/gh-actions-python-tests&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;🧰 What is GitHub Actions?&lt;/strong&gt;&lt;br&gt;
GitHub Actions is GitHub’s native CI/CD tool that lets you automate workflows across the development lifecycle: from testing and building to deployment.&lt;/p&gt;

&lt;p&gt;Why use it as a Testing Management Tool?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Seamlessly integrates with your GitHub repo.&lt;/li&gt;
&lt;li&gt;Supports all testing frameworks (pytest, unittest, etc.).&lt;/li&gt;
&lt;li&gt;Automates testing on every push or PR.&lt;/li&gt;
&lt;li&gt;Offers clear visibility via workflow status and logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📦 Project Overview&lt;/strong&gt;&lt;br&gt;
We’ll use a simple Python project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A module with a math function (&lt;code&gt;calculator.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Unit tests using unittest (&lt;code&gt;test_calculator.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A GitHub Actions workflow to run the tests automatically&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  📁 Project structure:
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gh-actions-python-tests/
│
├── calculator/
│   └── calculator.py
├── tests/
│   └── test_calculator.py
├── .github/
│   └── workflows/
│       └── test.yml
├── requirements.txt
└── README.md

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

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;🧪 1. The Python Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;calculator.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def add(a, b):
    return a + b

def subtract(a, b):
    return a - b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;test_calculator.py&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import unittest
from calculator.calculator import add, subtract

class TestCalculator(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(3, 2), 5)

    def test_subtract(self):
        self.assertEqual(subtract(10, 4), 6)

if __name__ == '__main__':
    unittest.main()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;⚙️ 2. GitHub Actions Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll create a CI workflow to automatically install dependencies and run tests on each push or pull_request.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.github/workflows/test.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Python Unit Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: 3.11

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run unit tests
      run: python -m unittest discover -s tests

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

&lt;/div&gt;



&lt;p&gt;✅ This will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger on push/PR&lt;/li&gt;
&lt;li&gt;Use Python 3.11&lt;/li&gt;
&lt;li&gt;Run tests with &lt;code&gt;unittest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;📊 3. Workflow in Action&lt;/strong&gt;&lt;br&gt;
Once pushed to GitHub, go to the "Actions" tab to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live logs of each CI run&lt;/li&gt;
&lt;li&gt;Summary of passed/failed tests&lt;/li&gt;
&lt;li&gt;Direct links to PRs or commits related to the run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers are alerted to test failures immediately.&lt;/li&gt;
&lt;li&gt;Every change is automatically validated.&lt;/li&gt;
&lt;li&gt;Test coverage becomes part of the development process.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌍 Real-world Examples from the Community
&lt;/h2&gt;

&lt;p&gt;To reinforce the relevance of GitHub Actions as a testing management tool, here are some public repositories by developers using it with Python:&lt;/p&gt;

&lt;p&gt;Repository: Description (Link)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pallets/flask: Official Flask repository. Uses GitHub Actions to run tests with tox and multiple Python versions.    &lt;a href="https://github.com/pallets/flask/actions" rel="noopener noreferrer"&gt;🔗 GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;tiangolo/fastapi: FastAPI framework by Sebastián Ramírez. Contains automated testing workflows.    &lt;a href="https://github.com/fastapi/fastapi/actions" rel="noopener noreferrer"&gt;🔗 GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;psf/requests: The famous HTTP library requests uses GitHub Actions to test on all major platforms.   &lt;a href="https://github.com/psf/requests/actions" rel="noopener noreferrer"&gt;🔗 GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;pytest-dev/pytest: The pytest testing framework runs all its test automation through GitHub Actions. &lt;a href="https://github.com/pytest-dev/pytest/actions" rel="noopener noreferrer"&gt;🔗 GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;kennethreitz/pipenv: Manages virtual environments and dependencies; runs tests with GitHub Actions.  &lt;a href="https://github.com/pypa/pipenv/actions" rel="noopener noreferrer"&gt;🔗 GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These repositories show how powerful and widely adopted GitHub Actions has become for testing management in Python ecosystems.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏁 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;GitHub Actions allows for rapid feedback, automated validation, and easy integration — making it a robust and developer-friendly Testing Management Tool.&lt;/p&gt;

&lt;p&gt;🔗 Don’t forget to explore the repo here:&lt;br&gt;
👉 &lt;a href="//github.com/srg-cp/gh-actions-python-tests"&gt;Repo github&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🧪 API Testing with Swagger: Functional, Security, and Performance in One Powerful Tool</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Thu, 05 Jun 2025 17:45:26 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/api-testing-with-swagger-functional-security-and-performance-in-one-powerful-tool-3ogg</link>
      <guid>https://dev.to/sergiocolqueponce/api-testing-with-swagger-functional-security-and-performance-in-one-powerful-tool-3ogg</guid>
      <description>&lt;h2&gt;
  
  
  🔍 Introduction
&lt;/h2&gt;

&lt;p&gt;API testing is crucial in today’s software development lifecycle. Among many available tools, Swagger stands out for its integration with the OpenAPI Specification and its powerful capabilities for functional, performance, and security testing.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show how to use Swagger Inspector for testing APIs with real-world examples. We’ll also explore how Swagger integrates into CI/CD pipelines and supports teams in large-scale API development.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What is Swagger?
&lt;/h2&gt;

&lt;p&gt;Swagger is a suite of open-source and commercial tools for API development and testing, based on the OpenAPI Specification.&lt;/p&gt;

&lt;p&gt;Key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Swagger Inspector: Test endpoints and validate responses interactively.&lt;/li&gt;
&lt;li&gt;SwaggerHub: Collaborative platform for designing and documenting APIs.&lt;/li&gt;
&lt;li&gt;Swagger UI: Visualize and interact with APIs.&lt;/li&gt;
&lt;li&gt;Swagger Codegen: Generate client libraries from API definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ✅ Why Choose Swagger for API Testing?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Supports REST, SOAP, and GraphQL&lt;/li&gt;
&lt;li&gt;Auto-generates assertions based on OpenAPI&lt;/li&gt;
&lt;li&gt;Schema validation out of the box&lt;/li&gt;
&lt;li&gt;Performance/load testing with synthetic data&lt;/li&gt;
&lt;li&gt;Free and open source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 Real-World Example: Testing a Public API
&lt;/h2&gt;

&lt;p&gt;We'll test the &lt;strong&gt;JSONPlaceholder&lt;/strong&gt; API () using &lt;strong&gt;Swagger&lt;/strong&gt; &lt;strong&gt;Inspector&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://jsonplaceholder.typicode.com/posts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Open &lt;a href="https://inspector.swagger.io" rel="noopener noreferrer"&gt;Swagger Inspector&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Step 2:&lt;/strong&gt; Send a GET request&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://jsonplaceholder.typicode.com/posts/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Analyze the response&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status Code: 200&lt;/li&gt;
&lt;li&gt;Response Time: ~50ms&lt;/li&gt;
&lt;li&gt;Content-Type: &lt;code&gt;application/json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Body:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere...",
  "body": "quia et suscipit..."
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Add Assertions&lt;br&gt;
Swagger automatically suggests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Status is 200 OK&lt;/li&gt;
&lt;li&gt;Response body contains &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Header contains &lt;code&gt;Content-Type: application/json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can customize and add more validations manually.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚙️ Schema Validation Example
&lt;/h2&gt;

&lt;p&gt;When importing an OpenAPI definition, Swagger Inspector will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate endpoint responses against the schema&lt;/li&gt;
&lt;li&gt;Highlight mismatches and missing fields&lt;/li&gt;
&lt;li&gt;Generate test assertions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paths:
  /posts/{id}:
    get:
      summary: Get post by ID
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Post'
components:
  schemas:
    Post:
      type: object
      properties:
        id:
          type: integer
        userId:
          type: integer
        title:
          type: string
        body:
          type: string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 Integrating Swagger Tests into CI/CD
&lt;/h2&gt;

&lt;p&gt;Swagger Inspector allows exporting tests into ReadyAPI or Postman-compatible collections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Export test suite&lt;/li&gt;
&lt;li&gt;Integrate in Jenkins/GitHub Actions&lt;/li&gt;
&lt;li&gt;Run assertions automatically after deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run Swagger API Tests
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Download tests
      run: curl -O swagger-tests.json
    - name: Run tests
      run: newman run swagger-tests.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ✍️ Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Swagger is more than just a documentation tool — it’s a powerful API testing framework that helps ensure quality, reliability, and performance from the first line of the API spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Swagger Official Website: &lt;a href="https://swagger.io/" rel="noopener noreferrer"&gt;https://swagger.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JSONPlaceholder API: &lt;a href="https://jsonplaceholder.typicode.com/" rel="noopener noreferrer"&gt;https://jsonplaceholder.typicode.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Top 10 API Testing Tools: &lt;a href="https://alicealdaine.medium.com/top-10-api-testing-tools-rest-soap-services-5395cb03cfa9" rel="noopener noreferrer"&gt;https://alicealdaine.medium.com/top-10-api-testing-tools-rest-soap-services-5395cb03cfa9&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>swagger</category>
      <category>api</category>
    </item>
    <item>
      <title>🔐 Strengthen Your IaC with Terrascan: A Complete Guide for Terraform Security</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Mon, 21 Apr 2025 04:54:33 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/strengthen-your-iac-with-terrascan-a-complete-guide-for-terraform-security-2cab</link>
      <guid>https://dev.to/sergiocolqueponce/strengthen-your-iac-with-terrascan-a-complete-guide-for-terraform-security-2cab</guid>
      <description>&lt;h2&gt;
  
  
  🛠️ Introduction
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code (IaC) brings speed and consistency to cloud deployments—but it also opens the door to misconfigurations and vulnerabilities. Just like application code, your IaC must be secured.&lt;/p&gt;

&lt;p&gt;In this article, we’ll dive into Terrascan, a powerful open-source SAST tool for IaC, and show how to use it to analyze and secure your Terraform infrastructure before it ever hits production.&lt;/p&gt;

&lt;p&gt;By the end, you’ll know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Terrascan is and how it works.&lt;/li&gt;
&lt;li&gt;How to use it on a real Terraform project.&lt;/li&gt;
&lt;li&gt;How to automate it using GitHub Actions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔍 What Is Terrascan?
&lt;/h2&gt;

&lt;p&gt;Terrascan is a static code analyzer developed by Tenable that detects security and compliance violations in your Terraform (as well as Kubernetes, CloudFormation, ARM, and more) code.&lt;/p&gt;

&lt;p&gt;It uses Rego policies from Open Policy Agent (OPA) to enforce security best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎯 Terrascan Highlights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports over 500 built-in policies.&lt;/li&gt;
&lt;li&gt;Scans Terraform HCL files.&lt;/li&gt;
&lt;li&gt;Integrates with CI/CD pipelines.&lt;/li&gt;
&lt;li&gt;Detects AWS, Azure, GCP, and Kubernetes misconfigurations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ Step-by-Step Demo
&lt;/h2&gt;

&lt;p&gt;Let’s walk through scanning a vulnerable Terraform project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📁 Step 1: Prepare Vulnerable Terraform Code&lt;/strong&gt;&lt;br&gt;
We’ll create an insecure AWS S3 bucket in &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "example" {
  bucket = "my-unsecure-bucket"
  acl    = "public-read" # ❌ Publicly accessible!
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This configuration violates AWS security best practices because it allows &lt;br&gt;
public access.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔍 Step 2: Install Terrascan&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install via Homebrew (macOS/Linux):&lt;br&gt;
&lt;code&gt;brew install terrascan&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or use Docker:&lt;br&gt;
&lt;code&gt;docker run --rm -v $(pwd):/iac tenable/terrascan scan -t terraform&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or download the binary from the official &lt;a href="https://github.com/tenable/terrascan" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Step 3: Run Terrascan Locally&lt;/strong&gt;&lt;br&gt;
To scan your code:&lt;br&gt;
&lt;code&gt;terrascan scan -t terraform -d .&lt;/code&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Violation detected:
 - Rule Name: AWS S3 bucket should not have public READ access.
 - Severity: HIGH
 - File: main.tf
 - Line: 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;✅ Terrascan catches the misconfiguration before deployment!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧹 Step 4: Fix the Issue&lt;/strong&gt;&lt;br&gt;
Replace the bucket ACL with a private setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_s3_bucket" "example" {
  bucket = "my-secure-bucket"
  acl    = "private" # ✅ Private access only
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scan again and verify no violations are found:&lt;br&gt;
&lt;code&gt;terrascan scan -t terraform -d .&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ Bonus: Automate with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Terrascan integrates easily with CI/CD.&lt;br&gt;
Create a &lt;code&gt;.github/workflows/terrascan.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Terraform Security Scan

on:
  push:
    branches: [main]
  pull_request:

jobs:
  terrascan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Install Terrascan
        run: |
          curl -L https://github.com/tenable/terrascan/releases/latest/download/terrascan_linux_amd64 -o terrascan
          chmod +x terrascan
          sudo mv terrascan /usr/local/bin/

      - name: Run Terrascan
        run: terrascan scan -t terraform -d .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;🚀 Every push or PR will now trigger a security scan!&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💻 GitHub Repository
&lt;/h2&gt;

&lt;p&gt;👉 Demo Code + GitHub Actions ready to deploy: 🔗 &lt;a href="https://github.com/Sergio-Colque-Ponce/terrascan_demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vulnerable and fixed Terraform files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.github/workflows/terrascan.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;README instructions&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Terrascan makes it incredibly easy to integrate SAST into your Infrastructure as Code workflows. By catching risks early, you ensure cloud security and compliance—without slowing development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Key Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast and free.&lt;/li&gt;
&lt;li&gt;Over 500 built-in policies.&lt;/li&gt;
&lt;li&gt;Works locally and in CI/CD.&lt;/li&gt;
&lt;li&gt;Secures Terraform, Kubernetes, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start using Terrascan today and protect your infrastructure from the start!&lt;/p&gt;




&lt;h2&gt;
  
  
  💬 Got feedback?
&lt;/h2&gt;

&lt;p&gt;Drop a comment below or share how you’re securing your IaC.&lt;br&gt;
Happy scanning! 👨‍💻🛡️&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>terrascan</category>
    </item>
    <item>
      <title>🐍 Secure Your Python Code in Minutes Using Bandit (A Practical Guide)</title>
      <dc:creator>Sergio Alberto Colque Ponce</dc:creator>
      <pubDate>Mon, 21 Apr 2025 00:03:44 +0000</pubDate>
      <link>https://dev.to/sergiocolqueponce/secure-your-python-code-in-minutes-using-bandit-a-practical-guide-5fp7</link>
      <guid>https://dev.to/sergiocolqueponce/secure-your-python-code-in-minutes-using-bandit-a-practical-guide-5fp7</guid>
      <description>&lt;p&gt;In today’s fast-paced software world, security isn’t something you can afford to overlook. The earlier you integrate security into your development process, the better. One of the easiest and most effective ways to start is by applying &lt;strong&gt;SAST (Static Application Security Testing)&lt;/strong&gt;. In this article, I’ll walk you through using Bandit, a lightweight but powerful open-source tool designed specifically to catch common security issues in Python applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 What is Bandit?
&lt;/h2&gt;

&lt;p&gt;Bandit is a static code analyzer for Python projects. It was created to find common security flaws in your code before it even runs.&lt;/p&gt;

&lt;p&gt;Bandit scans your Python files and warns you about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use of insecure functions (e.g., eval, exec, subprocess)&lt;/li&gt;
&lt;li&gt;Hardcoded passwords or credentials&lt;/li&gt;
&lt;li&gt;Insecure usage of third-party libraries&lt;/li&gt;
&lt;li&gt;Potential injection vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s easy to integrate into your workflow and doesn’t require much setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Getting Started with Bandit
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Step 1: Install Bandit&lt;/strong&gt;&lt;br&gt;
Just run:&lt;br&gt;
&lt;code&gt;pip install bandit&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That’s it — Bandit is now ready to scan your code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Step 2: Create a Vulnerable Python Script (For Testing)&lt;/strong&gt;&lt;br&gt;
Here’s a simple example of a Python script that includes a few insecure patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# vulnerable_app.py

import subprocess

def run_command():
    command = input("Enter a shell command: ")
    subprocess.call(command, shell=True)

run_command()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This script allows arbitrary command execution using user input — a classic &lt;strong&gt;command injection&lt;/strong&gt; vulnerability.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Step 3: Run Bandit on Your Code&lt;/strong&gt;&lt;br&gt;
Use the terminal to analyze the file:&lt;br&gt;
&lt;code&gt;bandit -r vulnerable_app.py&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This will generate a report highlighting the insecure use of &lt;strong&gt;subprocess.call()&lt;/strong&gt; with &lt;strong&gt;shell=True&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 Sample Output
&lt;/h2&gt;

&lt;p&gt;Bandit will output something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[bandit]    INFO    running on Python 3.11.0
&amp;gt;&amp;gt; Issue: [B602:subprocess_popen_with_shell_equals_true] Subprocess call with shell=True identified
   Severity: High   Confidence: High
   File: vulnerable_app.py   Line: 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Now you’ve got real insight into potential issues — before they hit production.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Fixing the Issue
&lt;/h2&gt;

&lt;p&gt;To make this safer, we can refactor the function like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import subprocess
import shlex

def run_command():
    command = input("Enter a shell command: ")
    subprocess.call(shlex.split(command))

run_command()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Bandit will now report a much cleaner bill of health.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 Bonus: GitHub Actions Integration
&lt;/h2&gt;

&lt;p&gt;Want to automate Bandit checks with each push? Add this to your &lt;code&gt;.github/workflows/security.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Python Security Scan

on: [push, pull_request]

jobs:
  bandit-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install Bandit
        run: pip install bandit
      - name: Run Bandit
        run: bandit -r . -f txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This will automatically scan your repo and catch issues before merging code.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Demo Repository
&lt;/h2&gt;

&lt;p&gt;👉 Check out the full working example here:&lt;br&gt;
🔗&lt;a href="https://github.com/Sergio-Colque-Ponce/python-bandit-demo" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎥 Video Walkthrough
&lt;/h2&gt;

&lt;p&gt;Watch this short 5-minute video explaining the code and Bandit in action:&lt;br&gt;
🎬&lt;a href="https://www.tiktok.com/404" rel="noopener noreferrer"&gt;Watch on TikTok&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;Security tools like Bandit make it easy and quick to add a layer of protection to your code. Whether you’re working on a solo script or a team project, catching vulnerabilities early helps you avoid trouble later.&lt;/p&gt;

&lt;p&gt;Give Bandit a try — it only takes minutes and could save you hours of debugging or patching later!&lt;/p&gt;




&lt;p&gt;💬 Have you tried Bandit? Got questions or tips? Drop them in the comments!&lt;/p&gt;

</description>
      <category>bandit</category>
      <category>sast</category>
      <category>python</category>
    </item>
  </channel>
</rss>
