<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Rob Walters - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Rob Walters - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 07 Aug 2026 00:35:25 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/rwalters/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ What to Do if You've Outgrown Your Cron Job Scheduler ]]>
                </title>
                <description>
                    <![CDATA[ Most developers begin their automation journey similarly. They create a script that performs a helpful task, such as pulling data from an API, resizing a batch of images, or emailing a report, and the ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-to-do-if-you-ve-outgrown-your-cron-job-scheduler/</link>
                <guid isPermaLink="false">6a63cd4ce956973f287e1685</guid>
                
                    <category>
                        <![CDATA[ Workflow Automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Orchestration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ orchestration-platform ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ it automation tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cron ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cronjob ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cron Job Scheduling ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI-automation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rob Walters ]]>
                </dc:creator>
                <pubDate>Fri, 24 Jul 2026 20:38:36 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/af95f928-4ab0-4e7d-99c9-fae3fb328a83.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers begin their automation journey similarly. They create a script that performs a helpful task, such as pulling data from an API, resizing a batch of images, or emailing a report, and then schedule it to run each morning.</p>
<p>This leads them to add a line to their crontab, which gives them a sense of control. Now, their computer runs the script automatically while they sleep.</p>
<p>For a while, that's enough. Then it isn't.</p>
<p>Maybe the backup script failed silently at 3 a.m., and you didn't find out until you needed it later that day, or you developed the perfect script that ran smoothly in your terminal to find it failing when scheduled in cron.</p>
<p>If any of that sounds familiar, congratulations: you've outgrown cron. You're not alone, and this article aims to help you feel understood and ready for better solutions.</p>
<p>This article is about what comes next. We'll look at exactly where cron runs out of road, what 'workflow orchestration' actually means underneath the buzzword, and then build a real workflow step by step so the concepts stick.</p>
<p>By the end, you'll feel more confident and empowered to choose the right tools for complex scheduling challenges.</p>
<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-cron-is-and-what-its-genuinely-good-at">What Cron is, and What it's Genuinely Good at</a></p>
</li>
<li><p><a href="#heading-the-four-walls-youll-hit-with-cron">The Four Walls You'll Hit with Cron</a></p>
</li>
<li><p><a href="#heading-can-workflow-orchestration-save-the-day">Can Workflow Orchestration Save the Day?</a></p>
</li>
<li><p><a href="#heading-kestra-primer">Kestra Primer</a></p>
</li>
<li><p><a href="#heading-how-to-use-kestra">How to Use Kestra</a></p>
</li>
<li><p><a href="#heading-where-to-go-next">Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-what-cron-is-and-what-its-genuinely-good-at">What Cron is, and What it's Genuinely Good at</h2>
<p><strong>Cron</strong> is the system background service (daemon) that runs scheduled tasks. Crontab (cron table) is the configuration file or command utility used to write and manage those task schedules' time-based job scheduler that has shipped with Unix-like systems since the 1970s. It's available on today’s Linux distributions as well as on Macs.</p>
<p>With cron, you specify a schedule and a command, and it runs the command at that time. The schedule uses the famous five-field syntax:</p>
<pre><code class="language-plaintext">┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–6)
│ │ │ │ │
0  9  *  1 5  /usr/bin/python3 /home/me/daily_report.py
</code></pre>
<p>Note: The line <code>0 9 * 1 5&nbsp; /usr/bin/python3 /home/me/daily_report.py</code> means "run daily_report.py at 9:00 a.m. on weekdays." The syntax is terse, ubiquitous, and (credit where it's due) rock-solid for what it does.</p>
<p>And here's the important part: <strong>cron is not bad.</strong> For a single, self-contained, failure-tolerant task on one machine, it's the right tool. But for more complex workflows, a purpose-built orchestration tool can provide the reliability and visibility you need, helping you feel more in control of your automation.</p>
<h2 id="heading-the-four-walls-youll-hit-with-cron"><strong>The Four Walls You'll Hit with Cron</strong></h2>
<p>The trouble with cron starts when your automation stops being a single self-contained task. As your scripts grow, they form more complex systems where you run into the same four limitations, more or less in this order.</p>
<h3 id="heading-wall-1-dependencies-between-steps">Wall 1: Dependencies Between Steps</h3>
<p>Your morning routine grows from one script into three. Consider an ETL scenario with three scripts:</p>
<ol>
<li><p><strong>extract.py</strong> pulls yesterday's orders from an API.</p>
</li>
<li><p><strong>transform.py</strong> cleans the data and computes totals.</p>
</li>
<li><p><strong>load.py</strong> writes the result into the analytics database.</p>
</li>
</ol>
<p>Each step depends on the one before it. The obvious cron approach is to guess at the timing:</p>
<pre><code class="language-plaintext">0 2 * * *  python extract.py
0 3 * * *  python transform.py
0 4 * * *  python load.py
</code></pre>
<p>You're now hoping the extract finishes within an hour, so that the transform has something to work with. On the day the API is slow, and extract takes 70 minutes, transform runs against stale or missing data and quietly produces garbage. Cron has no concept of "run B only after A succeeds." It only knows wall-clock time.</p>
<h3 id="heading-wall-2-failure-handling-and-retries">Wall 2: Failure Handling and Retries</h3>
<p>Networks blip. APIs return 503 status codes (Service Unavailable). Databases drop connections. A robust job needs to detect a failure and retry. Maybe three times, or with increasing delays between attempts, so you don't hammer a struggling service.</p>
<p>With cron, retry logic is your problem. You end up bolting it onto every script by hand: try/except blocks, sleep calls, a counter, and a flag file so the next cron tick knows whether the previous one finished. Multiply that across a dozen jobs, and you've written a small, buggy, undocumented orchestration engine.</p>
<h3 id="heading-wall-3-visibility">Wall 3: Visibility</h3>
<p>Ask yourself, did your jobs run last night? Which ones succeeded? How long did each take? Is the slowdown in the extract step or the load step?</p>
<p>With cron, the honest answer is "I'd have to go read some log files, if the script even wrote any." The first task is finding the cron job log files themselves.</p>
<p>Finding them can be complicated: cron's logs can be in /var/log/syslog on one system and /var/log/cron on another, assuming logging is enabled. Meanwhile, your script's output only exists if you explicitly redirect stdout and stderr. As a result, you have to search through system logs to verify the job ran, then locate any separate output files that captured its print statements.</p>
<p>When a job fails, identifying the issue is even harder, as you must sift through interleaved logs from multiple runs, trying to determine which timestamp corresponds to the last execution and where it went wrong, often relying only on a non-zero exit code as a clue. There's no dashboard, no run history, no record of how long things took, and no alert when something breaks.</p>
<p>Failures are silent by default, the single most dangerous property a background job can have. You find out your pipeline has been broken for a week when someone downstream notices the numbers stopped updating.</p>
<h3 id="heading-wall-4-backfills-and-re-runs">Wall 4: Backfills and Re-runs</h3>
<p>Your analytics database has been live for two months when you discover a bug in <strong>transform.py</strong> that miscalculated totals. You've fixed the code. Now you need to rerun the pipeline for each day in those two months, processing its own slice of data.</p>
<p>This is a backfill, and with cron, it's a nightmare. Cron only ever runs "now." There's no built-in notion of "run this job as if it were March 14th, then March 15th, then..." So you write yet another throwaway script with a date loop, pray it's idempotent, and babysit it.</p>
<p>Notice the pattern across all four walls: each time, you end up reimplementing something poorly that a category of tools already solves well. That category is workflow orchestration.</p>
<h2 id="heading-can-workflow-orchestration-save-the-day"><strong>Can Workflow Orchestration Save the Day?</strong></h2>
<p>A workflow orchestrator manages workflows, which are sets of tasks with specific relationships, triggers, and failure protocols, along with observability for tracking outcomes.</p>
<p>While many solutions like Airflow, Dagster, Prefect, and Temporal are available, we'll focus on <a href="https://kestra.io">Kestra</a>, an open-source orchestrator. Kestra stands out from the pack by enabling developers to run, monitor, and manage workflows all from a single declarative layer compatible with any programming language and infrastructure, including public, private, or even air-gapped networks.</p>
<p>Kestra also provides enterprises with the control needed to ensure insights and regulatory compliance. With over 1,600 connectors available, you can build almost any data, infrastructure, or AI workflow you can imagine.</p>
<p>In this tutorial, we'll continue with the cron scheduler theme and build a simple ETL workflow that uses a cron-like schedule while addressing some of cron's shortcomings, such as execution sequence and error handling.</p>
<h2 id="heading-kestra-primer">Kestra Primer</h2>
<p>Kestra was born out of the engineering pain of creating Python code to achieve proper workflow orchestration.&nbsp;Rather than spend engineering hours crafting the right Python code for your Apache Airflow DAG, with Kestra, your workflow is a YAML file. Simply describe what should run in plain, declarative syntax. Store the file in Git, and deploy it like any other code.</p>
<p>There's no obscure UI logic building and no hidden state management. Just simple text that can be easily reviewed and diffed.&nbsp;The workflow you read is the workflow that runs.</p>
<p>The example YAML file in Figure 1 illustrates a scenario in which you want to move NoSQL data into an analytics-ready warehouse.</p>
<pre><code class="language-yaml">id: cassandra-to-bigquery
namespace: company.team

tasks:
  - id: query_cassandra
    type: io.kestra.plugin.cassandra.Query
    session:
      endpoints:
        - hostname: localhost
          port: 9042
      localDatacenter: datacenter1
    cql: |
      SELECT salary_id, work_year, experience_level, employment_type,
      job_title, salary, salary_currency, salary_in_usd, employee_residence,
      remote_ratio, company_location, company_size
      FROM test.salary
    fetchType: STORE

  - id: write_to_csv
    type: io.kestra.plugin.serdes.csv.IonToCsv
    from: "{{ outputs.query_cassandra.uri }}"

  - id: load_bigquery
    type: io.kestra.plugin.gcp.bigquery.Load
    from: "{{ outputs.write_to_csv.uri }}"
    destinationTable: my_project.my_dataset.my_table
    serviceAccount: "{{ secret('GCP_SERVICE_ACCOUNT_JSON') }}"
    projectId: my_project
    format: CSV
    csvOptions:
      fieldDelimiter: ","
      skipLeadingRows: 1
</code></pre>
<p>Figure 1: Cassandra to BigQuery Example</p>
<p>Without even knowing much about Kestra, the YAML file is simple and self-explanatory. Later in this post, we'll create a basic ETL flow and explain the significance of fields such as id and type. But first, let’s get an instance of Kestra running on your local machine.</p>
<h3 id="heading-how-to-set-up-kestra">How to Set Up Kestra</h3>
<p>Kestra is available as an open-source platform licensed under the Apache 2.0 license as well as an Enterprise offering <a href="https://kestra.io/docs/enterprise">additional features</a> and product support. In this tutorial, we'll use Docker to run Kestra locally using the latest version.</p>
<p>To spin up Kestra, run the following Docker command:</p>
<pre><code class="language-shell">docker run --pull=always --rm -it -p 8080:8080 
  --user=root \
  --name kestra \
  -v kestra_data:/app/storage \
  -v kestra_db:/app/data \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp:/tmp \
  kestra/kestra:latest server local
</code></pre>
<p>For other platforms such as Windows and Linux check out <a href="https://kestra.io/get-started">https://kestra.io/get-started</a>.</p>
<p>Once the containers are loaded, navigate to the Kestra UI at <a href="http://localhost:8080">http://localhost:8080</a>. The welcome screen will ask you to create an administrator.&nbsp;Create the user and finish the initial launch wizard.</p>
<p>Once that's complete, navigate to the Flows tab in the left panel, then click the Create button at the top right of the page. This will create a new flow using a sample template, as shown in the following figure:</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/0ed945f2-ae0b-4f4f-b274-a4f61d3fc255.png" alt="Kestra Flows showing Flow Code Editor" style="display:block;margin:0 auto" width="1734" height="814" loading="lazy">

<p>Figure 2: Flows page showing the new flow template</p>
<h3 id="heading-flows-in-kestra">Flows in Kestra</h3>
<p>In Kestra you define your workflow orchestration through Flows. You can create these Flows using YAML syntax in the UI, through a no-code editor in the UI, or programmatically through an API. In this tutorial, we'll create flows using YAML.</p>
<p>In Figure 2 above, you'll notice that a sample flow is already created to get you started.</p>
<p>The <strong>_id</strong>, <strong>namespace,</strong> and <strong>tasks</strong> are three required fields and are used to identify the flow within the Kestra environment and the task the flow should execute. Each flow lives in one namespace. Namespaces are like folders in a filesystem and are used to group flows and provide structure. Note that you can't change a flow’s namespace after creation.</p>
<h2 id="heading-how-to-use-kestra">How to Use Kestra</h2>
<h3 id="heading-step-1-a-simple-task-executed-on-a-schedule">Step 1: A Simple Task Executed on a Schedule</h3>
<p>Let's start by erasing the sample flow provided and replacing it with the following:</p>
<pre><code class="language-yaml">id: morning_report
namespace: tutorial

tasks:
  - id: say_hello
    type: io.kestra.plugin.core.log.Log
    message: "Good morning — the pipeline ran at {{ execution.startDate }}"
</code></pre>
<p>This example has the required id, namespace, and has one tasks field that logs a message. The message displayed uses a <a href="https://kestra.io/docs/expressions">pebble expression</a> (the command inside the&nbsp; {{ }} brackets) to show the startDate of the execution.</p>
<p>Pebble expressions are used to set values dynamically in flows. In this example, the start date of the execution will be inserted within the string.</p>
<p>To execute this flow, we need to first save it by clicking the Save button in the upper-right corner of the page. Once saved, click the Play button. You'll then be presented with the execute options page, as shown in Figure 3 below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/9e31e532-f41b-4921-916f-e803c7bcc900.png" alt="Execute Flow input dialog" style="display:block;margin:0 auto" width="1058" height="490" loading="lazy">

<p>Figure 3: Execute flow options</p>
<p>If this workflow had inputs such as a filename or URL, you can manually enter them here to test your flow. The modal also provides the curl command if you’d like to run the flow via the API instead of the UI.&nbsp;Click the Execute button to run the flow:</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/a7a2e91c-13f3-4c82-b704-3f2812348a09.png" alt="Flow execution log dialog" style="display:block;margin:0 auto" width="1592" height="940" loading="lazy">

<p>Figure 4: Flow execution log</p>
<p>The flow was simple and wrote the info message to the log file. If the flow had errors or warnings, you'd be able to see detailed execution logs on this page.</p>
<p>Now that we’ve created our first task, let’s schedule it with a cron mask. Add the following to the flow by clicking on the “Edit Flow” button at the top of the page.</p>
<p>Next, add the triggers section to the flow:</p>
<pre><code class="language-yaml">triggers:
  - id: every_minute
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "* * * * *"
</code></pre>
<p>Execute the flow.&nbsp; After a few minutes, check out the execution history by clicking on the “Execution” tab on the left side navbar.</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/e421c8b1-5aac-4524-934a-f0cc70cb5f8a.png" alt="Flow execution page" style="display:block;margin:0 auto" width="2048" height="359" loading="lazy">

<p>Figure 5: Flow execution page</p>
<p>Our flow now behaves similarly to a single task cron job. The flow has a triggers block with a Schedule trigger whose cron field is the exact same five-field syntax you already know. That last point matters: you're not throwing away what you learned. You're wrapping it in something that can grow as your needs change.</p>
<p>Notice that even in this simple Kestra example, you’ve gained something cron didn't offer. Every time this runs, it's recorded as an execution with a timestamp, a duration, and a status, and all logs are visible in the UI. That's Wall 3 (visibility) handled before we've even done anything interesting.</p>
<h3 id="heading-step-2-real-work-and-a-dependency">Step 2: Real Work, and a Dependency</h3>
<p>Now let's replace the simple task with the three-step extract/transform/load and have the orchestrator enforce the ordering rather than timing offsets.</p>
<pre><code class="language-yaml">id: csv_to_parquet
namespace: company.team
description: Download orders CSV, transform it with a Python script, and write the result to a Parquet file.

tasks:
  # Download a public CSV file into Kestra's internal storage
  - id: download_csv
    type: io.kestra.plugin.core.http.Download
    uri: https://huggingface.co/datasets/kestra/datasets/raw/main/csv/orders.csv

  # Transform the CSV with a simple Python script and write it out as Parquet
  - id: transform_to_parquet
    type: io.kestra.plugin.scripts.python.Script
    containerImage: ghcr.io/kestra-io/pydata:latest
    inputFiles:
      input.csv: "{{ outputs.download_csv.uri }}"
    outputFiles:
      - orders.parquet
    script: |
      import pandas as pd

      # Read the downloaded CSV
      df = pd.read_csv("input.csv")

      # --- simple transformation ---
      # Ensure numeric types and add a computed column
      df["total"] = df["quantity"] * df["price"]

      # Keep only orders above a small threshold as an example filter
      df = df[df["total"] &gt; 0]

      print(f"Rows after transform: {len(df)}")

      # Write the result to Parquet
      df.to_parquet("orders.parquet", index=False)

  # Log that the Parquet file was produced
  - id: log_output
    type: io.kestra.plugin.core.log.Log
    message: "Parquet file created: {{ outputs.transform_to_parquet.outputFiles['orders.parquet'] }}"
# Expose the Parquet file as a downloadable flow output.
# FILE-typed flow outputs appear on the execution's Overview tab with a download button.
outputs:
  - id: parquet_file
    type: FILE
    value: "{{ outputs.transform_to_parquet.outputFiles['orders.parquet'] }}"
</code></pre>
<p>Save the flow, then execute.</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/cd4e0202-4769-4e83-bf1d-c1dec91bae8e.png" alt="Execution results" style="display:block;margin:0 auto" width="2048" height="928" loading="lazy">

<p>Figure 6: Execution results</p>
<p>Two things just happened. First, tasks listed in sequence run in sequence. Transform_to_parquet only starts after download_csv succeeds, and log_output only after transform_to_parquet succeeds.&nbsp;If a failure occurs, the execution stops, and transform_to_parquet never touches stale data. That's <strong>Wall 1 (dependencies)</strong> gone, with no guesswork about timing.</p>
<p>Second, notice the expression within the flow {{ outputs.download_csv.uri }}. Tasks can pass data and metadata to subsequent tasks using expressions like this. That wiring turns a list of scripts into an actual pipeline.</p>
<h3 id="heading-step-3-surviving-failure-with-retries">Step 3: Surviving Failure with Retries</h3>
<p>Now consider the scenario where the download_csv task encounters a network issue, and the flow is unable to download the latest data. Let's make that task resilient declaratively by adding a retry section to the download_csv task:</p>
<pre><code class="language-yaml">- id: download_csv
    type: io.kestra.plugin.core.http.Download
    uri: https://huggingface.co/datasets/kestra/datasets/raw/main/csv/orders.csv
    retry:
      type: constant
      maxAttempts: 5
      interval: PT10S
</code></pre>
<p>That's the whole retry policy. If the download fails, Kestra waits and tries again up to 5 times, with a 10-second delay (PT10S is ISO-8601 for "10 seconds"). There are no counters, sleep calls, or flag files. <strong>Wall 2 (failure handling)</strong> is handled in 3 lines that read like a sentence.</p>
<p>To test this, drop the “s” from orders.csv and rerun the flow. You can see the execution showing retrying.</p>
<img src="https://cdn.hashnode.com/uploads/covers/67c9ccd99fc59c702c3edd73/4ab1a70c-9ada-4c99-aec0-5f5ccc48975a.png" alt="Execution dialog showing retry" style="display:block;margin:0 auto" width="2048" height="552" loading="lazy">

<p>Figure 7: Execution showing retry</p>
<p>If all attempts fail, you'll probably want to be notified. Let’s add a flow-level error handler that runs only when something in the workflow fails:</p>
<pre><code class="language-yaml">errors:
  - id: notify_failure
    type: io.kestra.plugin.notifications.slack.SlackIncomingWebhook
    url: "{{ secret('SLACK_WEBHOOK') }}"
    payload: |
      {"text": "orders_pipeline failed on execution {{ execution.id }}"}
</code></pre>
<p>Now a broken pipeline pings a Slack channel instead of failing silently at 3 a.m. Note that secrets are protected within flows via the <a href="https://kestra.io/docs/how-to-guides/secrets">secret</a> expression.</p>
<h3 id="heading-step-4-triggering-on-events-not-just-time">Step 4: Triggering on Events, Not Just Time</h3>
<p>Schedules are only one kind of trigger. Suppose orders don't arrive on a fixed timetable. Instead, a file lands in cloud storage whenever an upstream system feels like it.</p>
<p>Polling on a cron schedule ("check every 5 minutes, exit if nothing's there") is wasteful and laggy. Event triggers are the better model: run the workflow when the thing happens.</p>
<p>Conceptually, instead of adding a scheduled trigger similar to the one added in the previous example:</p>
<pre><code class="language-yaml">triggers:
  - id: every_minute
    type: io.kestra.plugin.core.trigger.Schedule
    cron: "* * * * *"
</code></pre>
<p>You can add a trigger that fires when a new object appears in an S3 bucket:</p>
<pre><code class="language-yaml">triggers:
  - id: new_s3_object
    type: io.kestra.plugin.aws.s3.Trigger
    interval: "PT1M"
    accessKeyId: "{{ secret('AWS_ACCESS_KEY_ID') }}"
    secretKeyId: "{{ secret('AWS_SECRET_KEY_ID') }}"
    region: "eu-central-1"
    bucket: "my-bucket"
    prefix: "incoming/"
    on: CREATE
    action: NONE
</code></pre>
<p>Alternatively, you could have a trigger that exposes a webhook URL you can POST to start execution, or <a href="https://kestra.io/docs/how-to-guides/realtime-triggers">real-time triggers</a> that listen to streaming services such as Kafka queues. In any of these trigger scenarios, the workflow body stays identical. Your automation can now respond to the world rather than just watching the clock.</p>
<h3 id="heading-step-5-backfilling-the-past">Step 5: Backfilling the Past</h3>
<p>Finally, consider the bug-in-transform scenario. You've fixed the calculation and need to re-run the pipeline for every day of the last two months.&nbsp;This would be painful in cron.</p>
<p>In an orchestrator, a <a href="https://kestra.io/docs/concepts/backfill">backfill</a> is a first-class operation on a scheduled workflow: you pick a start and end date, and it generates one execution per scheduled interval across that range. Each execution is aware of the date it represents, through an expression like {{ trigger.date }}. Your transform step can use that date to fetch and process the correct slice of data.</p>
<p>This is where idempotency stops being academic. Because a backfill re-runs days you may have already processed, your data load step should write with an "insert or replace" semantic keyed on the date, so running March 14th, for example, twice leaves the database in the same state as running it once.</p>
<p>Design for that and backfills become routine instead of terrifying. <strong>Wall 4 (backfills)</strong> is handled, but only if you've held up your end with idempotent tasks.</p>
<h2 id="heading-where-to-go-next"><strong>Where to Go Next</strong></h2>
<p>The best way to internalize all of this is to take an existing cron job and rebuild it as a proper workflow. Start with the one-task version, confirm it runs and appears in the run history, then add a second dependent task, a retry policy, and a failure alert.</p>
<p>Each step maps to one of the four walls, and you'll feel the difference immediately the first time a task fails, retries itself, and recovers without waking you up.</p>
<p>You don't need to write every line from scratch. Kestra provides a library of <strong>Blueprints</strong> that are hundreds of ready-made, copy-pasteable flows that you can browse at <a href="https://kestra.io/blueprints">kestra.io/blueprints</a> or access directly in your instance under the Blueprints tab.</p>
<p>Each Blueprint is a complete, executable example with an explanation of its functionality and how to extend it. This allows you to start with a close version of your goal and modify it rather than guessing at the syntax.</p>
<p>Cron showed that computers can operate while you're asleep. Orchestration takes it further, ensuring they do so reliably, in order, and visibly. This shift transforms the idea from merely writing a script to managing a full system.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Orchestrating AWS Lambda with GraphQL and Apollo Connectors ]]>
                </title>
                <description>
                    <![CDATA[ AWS Lambda is a computing service that enables you to run arbitrary code functions without needing to provision, manage, or scale servers. It’s often used in the logic tier of a multi-tier architecture to handle tasks such as processing files in S3 o... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-orchestrate-aws-lambda-with-graphql-and-apollo-connectors/</link>
                <guid isPermaLink="false">67e2d5e080e11112e050be00</guid>
                
                    <category>
                        <![CDATA[ GraphQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ aws lambda ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Apollo GraphQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rob Walters ]]>
                </dc:creator>
                <pubDate>Tue, 25 Mar 2025 16:12:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742917115054/07184be6-5384-4861-a676-b72c06ff7c65.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>AWS Lambda is a computing service that enables you to run arbitrary code functions without needing to provision, manage, or scale servers. It’s often used in the logic tier of a multi-tier architecture to handle tasks such as processing files in S3 or performing CRUD operations on a database.</p>
<p>AWS also offers an API Gateway, allowing developers to invoke AWS Lambda functions, which provides enhanced security and performance features like rate limiting. But even with the API Gateway, you have to coordinate these microservices, as your client applications likely each have unique data needs. Data might need to be transformed, filtered, or combined before it is returned to the client. </p>
<p>These orchestration tasks can reduce your productivity and take time and effort away from solving the business problem your application is trying to solve. </p>
<p>Apollo GraphQL is an API orchestration layer that helps teams ship new features faster and more independently by composing any number of underlying services and data sources into a single endpoint. This allows clients on-demand access to precisely what the experience needs, regardless of the source of that data.</p>
<p>This article will teach you how to orchestrate AWS Lambda functions using Apollo GraphQL. Specifically, here’s what we will cover:</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-graphql-primer">GraphQL Primer</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tutorial-overview">Tutorial Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-section-1-create-the-aws-resources">Section 1: Create the AWS Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-section-2-create-an-apollo-connector">Section 2: Create an Apollo Connector</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-section-3-how-to-use-apollo-sandbox">Section 3: How to Use Apollo Sandbox</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-graphql-primer">GraphQL Primer</h2>
<p>For those unfamiliar with GraphQL, here’s a primer that offers some background on the challenges GraphQL addresses and how data is typically managed through REST APIs in GraphQL before the emergence of Apollo Connectors. If you’re familiar with GraphQL, feel free to skip this section.</p>
<p>GraphQL is a query language for APIs. This query language and corresponding runtime enable clients to specify exactly the data they require, minimizing over-fetching and under-fetching.</p>
<p>In contrast to REST, which necessitates multiple endpoints for various data requirements, GraphQL streamlines queries into a single request, enhancing performance and reducing network latency.</p>
<p>GraphQL also uses a strongly typed schema. This improves API documentation and makes validation, early error detection, and immersive developer tooling easy. </p>
<p>To illustrate the difference between REST APIs and GraphQL, consider the following REST API call: /user/123</p>
<p>Response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-number">123</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Alice Johnson"</span>,
  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"alice@example.com"</span>,
  <span class="hljs-attr">"phone"</span>: <span class="hljs-string">"555-1234"</span>,
  <span class="hljs-attr">"address"</span>: {
    <span class="hljs-attr">"street"</span>: <span class="hljs-string">"123 Main St"</span>,
    <span class="hljs-attr">"city"</span>: <span class="hljs-string">"Springfield"</span>,
    <span class="hljs-attr">"state"</span>: <span class="hljs-string">"IL"</span>,
    <span class="hljs-attr">"zip"</span>: <span class="hljs-string">"62704"</span>
  },
  <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2022-01-01T12:00:00Z"</span>,
  <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-string">"2022-05-15T14:30:00Z"</span>,
  <span class="hljs-attr">"isAdmin"</span>: <span class="hljs-literal">false</span>
}
</code></pre>
<p>If you were only interested in the name and email, using REST would be a lot of data returned from the network to the client for no reason. Using GraphQL, the GraphQL query to return the name and email would be the following:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">query</span> {
  user(<span class="hljs-symbol">id:</span> <span class="hljs-number">123</span>) {
    name
    email
  }
}
</code></pre>
<p>The result set is just the data the client needs:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"data"</span>: {
    <span class="hljs-attr">"user"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Alice Johnson"</span>,
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"alice@example.com"</span>
    }
  }
}
</code></pre>
<p>This is a simple example showing the benefit of not over-fetching data, but GraphQL has many other advantages. One of them is the separation between client and server. Since both parties leverage and respect the GraphQL type schema, both teams can operate more independently with the back end defining where the data resides and the front end only asking for data it needs.    </p>
<p>So how does GraphQL know how to populate data for every field in your schema? It does this through <a target="_blank" href="https://www.apollographql.com/docs/apollo-server/data/resolvers">resolvers</a>. Resolvers can fetch data from a back-end databases or third-party API such as REST APIs, gRPC, and so on. These functions comprise procedural code compiled and maintained for each field in the schema. Thus, one field can have a resolver that queries a REST API and another can query a gRPC endpoint.</p>
<p>To illustrate resolvers, consider the example above. Let’s add a field, status, that queries a REST API to determine if the user is full-time, part-time, or terminated. </p>
<p>First we have defined our schema as:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> User {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String!
  <span class="hljs-symbol">email:</span> String!
  <span class="hljs-symbol">status:</span> String!  <span class="hljs-comment"># Need this from an external REST API</span>
}

<span class="hljs-keyword">type</span> Query {
  user(<span class="hljs-symbol">id:</span> ID!): User
}
</code></pre>
<p>The user query in this case will accept a user id and return a type User. The resolver function to support the data fetching resembles the following:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> resolvers = {
  <span class="hljs-attr">Query</span>: {
    <span class="hljs-attr">user</span>: <span class="hljs-keyword">async</span> (_, { id }) =&gt; {
      <span class="hljs-comment">// Fetch user details from one REST API</span>
      <span class="hljs-keyword">const</span> userResponse = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://api.company.com/users/<span class="hljs-subst">${id}</span>`</span>);
      <span class="hljs-keyword">const</span> userData = <span class="hljs-keyword">await</span> userResponse.json();

      <span class="hljs-comment">// Fetch employee status from another REST API</span>
      <span class="hljs-keyword">const</span> statusResponse = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`https://api.company.com/employees/<span class="hljs-subst">${id}</span>/status`</span>);
      <span class="hljs-keyword">const</span> statusData = <span class="hljs-keyword">await</span> statusResponse.json();

      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">id</span>: userData.id,
        <span class="hljs-attr">name</span>: userData.name,
        <span class="hljs-attr">email</span>: userData.email,
        <span class="hljs-attr">status</span>: statusData.status, <span class="hljs-comment">// e.g., "Full-Time", "Part-Time", "Terminated"</span>
      };
    },
  },
};
</code></pre>
<p>Notice that not only are there two fetches needed to obtain the information the query requires, but we also need to write procedural code and deploy it.</p>
<p>A better approach would be to declaratively specify to GraphQL where the REST API is located and what data to return. Apollo Connectors is the solution to this challenge, simplifying the process and allowing you to declaratively integrate REST API data without requiring code compilation and maintenance.</p>
<p>Now that you have a general idea of GraphQL and the challenges it addresses, let’s delve into the example we will build out.</p>
<h2 id="heading-tutorial-overview">Tutorial Overview</h2>
<p>In this tutorial, you will create two AWS Lambda functions that return product information, which are described as follows:</p>
<p>Products Request:</p>
<p>POST /2015-03-31/functions/products/invocations</p>
<p>Response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"statusCode"</span>: <span class="hljs-number">200</span>,
  <span class="hljs-attr">"body"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"RANQi6AZkUXCbZ"</span>,
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"OG Olive Putter - Blade"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"The traditional Block in a blade shape is made from a solid block of Olive wood. The head weight is approximately 360 grams with the addition of pure tungsten weights. Paired with a walnut center-line and white accents colors."</span>,
      <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://keynote-strapi-production.up.railway.app/uploads/thumbnail_IMG_9102_3119483fac.png"</span>
    },
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"RANYrWRy876AA5"</span>,
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Butter Knife Olive Putter- Blade"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"The traditional Block in a extremely thin blade shape (~1\") is made from a solid block of Olive wood. The head weight is approximately 330 grams with the addition of pure tungsten weights."</span>,
      <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://keynote-strapi-production.up.railway.app/uploads/thumbnail_IMG_9104_97c221e79c.png"</span>
    },...
</code></pre>
<p>Product-price request:</p>
<p>POST: /2015-03-31/functions/product-price/invocations</p>
<p>Response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"default_price"</span>: <span class="hljs-number">49900</span>,
  <span class="hljs-attr">"is_active"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"currency"</span>: <span class="hljs-string">"usd"</span>,
  <span class="hljs-attr">"billing_schema"</span>: <span class="hljs-string">"per_unit"</span>,
  <span class="hljs-attr">"recurring"</span>: {
    <span class="hljs-attr">"interval"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">"interval_count"</span>: <span class="hljs-number">3</span>
  }
}
</code></pre>
<p>To expose these two lambda microservices, you need to create API Gateway triggers. This involves either setting up a distinct API Gateway for each lambda or consolidating them under one or a few API Gateway instances with specified routes for each lambda.</p>
<p>Creating a trigger may feel tedious and repetitive in a microservices setup. But there is an alternative available. You could directly invoke those functions via REST using the InvokeFunction permission assigned to an IAM user. This article will show you this method and guide you through function creation, necessary AWS IAM permissions, and configuring the Apollo Connector to invoke the function.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along in this tutorial, you will need to have a basic understanding of AWS Lambda functions as well as AWS security. You’ll also need access to the following:</p>
<ul>
<li><p>An AWS account with permissions to create IAM Users and Policies</p>
</li>
<li><p>An Apollo GraphQL account, you can <a target="_blank" href="https://studio.apollographql.com/signup">sign up for a free plan here</a>.</p>
</li>
</ul>
<p>We will also use the following tools:</p>
<ul>
<li><p><a target="_blank" href="https://code.visualstudio.com/">VS Code</a>: Microsoft VS Code is a free source code editor from Microsoft</p>
</li>
<li><p><a target="_blank" href="https://www.apollographql.com/docs/rover/getting-started?utm_campaign=2025-03-20_installing-rover-doc-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Apollo Rover CLI</a>: Rover is the command-line interface for managing and maintaining graphs</p>
</li>
<li><p><a target="_blank" href="https://studio.apollographql.com/signup?utm_campaign=2025-03-19_studio-signup-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Apollo Studio</a>: A web-based portal used for managing all aspects of your graph </p>
</li>
<li><p><a target="_blank" href="https://www.apollographql.com/connectors-mapping-playground?utm_campaign=2025-03-20_connectors-mapping-playground-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Apollo Connectors Mapping Playground</a>: A website that takes a JSON document and helps developers create the selection mapping used with Apollo Connectors</p>
</li>
</ul>
<h2 id="heading-section-1-create-the-aws-resources">Section 1: Create the AWS Resources</h2>
<p>First, let’s configure our AWS environment, starting with security. In our scenario, we will create an IAM User, “ConnectorUser,” with access to an AWS Policy, “ConnectorLambdaPolicy,” with the minimum permissions needed to access the AWS Lambda functions.</p>
<p>Note that you could create user groups and assign permission policies to those groups in a production environment. But for this article, we are reducing the number of administrative steps to focus on the core integration with GraphQL.</p>
<h3 id="heading-step-1-create-an-aws-policy">Step 1: Create an AWS Policy</h3>
<p>To create a policy, navigate to IAM within the AWS Management console, then select “Policies” under Access Management. Click “Create Policy”. This will open the policy editor page, as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755417676/1025d04f-a712-4311-9669-ac38bd2fee50.jpeg" alt="specify permissions" class="image--center mx-auto" width="1558" height="720" loading="lazy"></p>
<p>Choose the “Lambda” service and under the Access level select “InvokeFunction” from the Write drop down menu as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755482285/1be204db-7b39-4c8f-ac7c-d461032f6887.jpeg" alt="InvokeFunction checkmarked" class="image--center mx-auto" width="1534" height="910" loading="lazy"></p>
<p>Under the Resources menu, you can choose either All ARNs or a specific option. It's a best practice to be as granular as possible when defining security configurations. In this example, let’s limit our selection to the “us-east-1” region by clicking on the “Specific” option and then “Add ARNs.” Enter “us-east-1” in the resource region and select “Any function name.”</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755564740/99e47ac8-4ce9-4ff3-94b6-105308526f56.jpeg" alt="Specify ARN dialog" class="image--center mx-auto" width="1598" height="824" loading="lazy"></p>
<p>With the policy created, we can assign an IAM user to that policy.</p>
<h3 id="heading-step-2-create-the-iam-user-and-attach-a-policy">Step 2: Create the IAM User and Attach a Policy</h3>
<p>Click on Users under “Access Management” then Create User. Provide a name for the user, “ConnectorUser”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755638499/ad782c39-a78f-4d68-b834-4e58dea9e35b.jpeg" alt="Permission policy" class="image--center mx-auto" width="1566" height="570" loading="lazy"></p>
<p>Next, select “Attach policies directly,” choose the policy we just created, “ConnectorLambdaPolicy,” and click “Create User.”</p>
<h3 id="heading-step-3-create-aws-lambda-functions">Step 3: Create AWS Lambda Functions</h3>
<p>In your AWS console, create a new NodeJS AWS Lambda function, “products”.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742754922858/b2a307c2-8b43-4417-b022-0113803a3b5d.jpeg" alt="AWS create function dialog" class="image--center mx-auto" width="1856" height="1036" loading="lazy"></p>
<p>Select “Node.JS” for the runtime then click “Create function”. Once created, paste in the the function code <a target="_blank" href="https://gist.github.com/RWaltersMA/25264ff22a5cbc26814a00dbb78a16e2">from this Gist</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755096066/90e96036-41cd-4b45-8841-0bb3acb5af6b.jpeg" alt="AWS function showing code source" class="image--center mx-auto" width="1990" height="1000" loading="lazy"></p>
<p>Repeat this process, creating another function for, “product-price” and use the function code <a target="_blank" href="https://gist.github.com/RWaltersMA/d75d9eb02264829c1392dbdf7f238bad">from this Gist</a>.</p>
<h2 id="heading-section-2-create-an-apollo-connector">Section 2: Create an Apollo Connector</h2>
<p>In this section, we will install the Apollo Rover CLI tool, create an Apollo Studio free tier account, and clone the Apollo Connectors repository. If you already have an Apollo environment available, you can skip steps 1 and 2.</p>
<h3 id="heading-step-1-install-rover">Step 1: Install Rover</h3>
<p>Rover is the command-line interface for managing and maintaining graphs. It also provides a modern hot-reloading experience for developing and running your connectors locally. If you don’t have Rover installed, install it by <a target="_blank" href="https://www.apollographql.com/docs/rover/getting-started?utm_campaign=2025-03-20_installing-rover-doc-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">following the steps here</a>.</p>
<h3 id="heading-step-2-create-an-apollo-studio-free-tier-account">Step 2: Create an Apollo Studio Free Tier Account</h3>
<p>Apollo Studio is a cloud-based management platform designed to explore, deliver, and collaborate on graphs. If you do not have an Apollo Studio account, create one on a free plan <a target="_blank" href="https://studio.apollographql.com/signup?utm_campaign=2025-03-19_studio-signup-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">by navigating here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755870123/4b38b025-064c-4a9a-b836-53a563152e43.jpeg" alt="Apollo Studio" class="image--center mx-auto" width="1560" height="753" loading="lazy"></p>
<h3 id="heading-step-3-clone-the-apollo-connectors-repository">Step 3: Clone the Apollo Connectors Repository</h3>
<p>To help you start your first Apollo Connector, a GitHub repository provides sample connectors and a template script. When run, this script will create all the necessary files and configurations you need to begin. </p>
<p>Go ahead and <a target="_blank" href="https://github.com/apollographql/connectors-community">clone the repository from here</a>.</p>
<p>Note: While not required, I recommended using VS Code, as this repo leverages VS Code-specific settings files.</p>
<h3 id="heading-step-4-create-a-env-file">Step 4: Create a .env File</h3>
<p>Before you run the Create Connectors template script, create a .env locally with a user API key from your Apollo Studio. You can <a target="_blank" href="https://studio.apollographql.com/user-settings/api-keys">create and obtain this key here</a>. Populating this .env file will add this API key to the connector template you create in the next step.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742755977271/860ef610-e802-4ec1-9cca-e881881a0968.jpeg" alt=".env file" class="image--center mx-auto" width="1554" height="109" loading="lazy"></p>
<h3 id="heading-step-5-create-your-new-connector-from-a-template">Step 5: Create Your New Connector from a Template</h3>
<p>Execute <code>npm start</code> and provide a location to create the connector template. You can use default values for the remaining questions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756030015/e6c3b535-8657-4a77-b353-b8546cfa9ac5.jpeg" alt="npmstart" class="image--center mx-auto" width="1536" height="402" loading="lazy"></p>
<p>This script will create all the necessary files to run a local Apollo GraphQL instance in the specified directory. Load the newly created connector using VS Code or your preferred code editor. You will return to this editor soon, but first, we need to obtain some access keys from AWS.</p>
<h3 id="heading-step-6-create-an-aws-access-key">Step 6: Create an AWS Access Key</h3>
<p>Since we connect to AWS using SigV4, we must create an AWS access key and enter the KEY values in the settings.json file. Return to the AWS IAM Console and select the <em>ConnectorUser</em> you created in Step 1.  Create a new access key by clicking on “Create access key”. </p>
<p>You will be presented with multiple options as far as where the use of this key will originate. Since we are first running locally, select “Third-party service” and then continue the wizard until you are presented with the key and secret key as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756092209/e7b33bd2-f6ca-4e78-bf83-8ed357860abd.jpeg" alt="retrieve access key dialog" class="image--center mx-auto" width="1548" height="344" loading="lazy"></p>
<p>Add the access key and secret access key to the settings.json file as “AWS_ACCESS_KEY_ID” and “AWS_SECRET_ACCESS_KEY” respectively.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756152443/31906e19-625d-446f-adde-b9618e8df61a.jpeg" alt="vscode settings file" class="image--center mx-auto" width="1140" height="624" loading="lazy"></p>
<p>You'll need to reload the window since VS Code only loads these files under the .vscode directory once. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756203631/8fb467fc-e8de-4f16-8907-622a12017d4f.jpeg" alt="vscode task window showing reload option" class="image--center mx-auto" width="1552" height="162" loading="lazy"></p>
<p>Note: In this step, we saved the key to the settings.json file. While this is acceptable for development, consider saving environment variables in .env files.</p>
<h3 id="heading-step-7-configure-the-graph">Step 7: Configure the Graph</h3>
<p>The supergraph.yaml file is used to define all the subgraphs that are part of this federation. Modify the <strong>supergraph.yaml</strong> file as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">federation_version:</span> <span class="hljs-string">=2.10.0</span>
<span class="hljs-attr">subgraphs:</span>
  <span class="hljs-attr">awsconnector:</span>
    <span class="hljs-attr">routing_url:</span> <span class="hljs-string">http://lambda</span>
    <span class="hljs-attr">schema:</span>
      <span class="hljs-attr">file:</span> <span class="hljs-string">connector.graphql</span>
</code></pre>
<h3 id="heading-step-8-configure-apollo-router">Step 8: Configure Apollo Router</h3>
<p>Apollo Router supports AWS SigV4 authentication. To configure the connector to use this, modify the <strong>router.yaml</strong> file and add an authentication section as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">authentication:</span>
  <span class="hljs-attr">connector:</span>
    <span class="hljs-attr">sources:</span>
      <span class="hljs-attr">awsconnector.lambda:</span>   <span class="hljs-comment"># subgraph name . connector source name</span>
        <span class="hljs-attr">aws_sig_v4:</span>
          <span class="hljs-attr">default_chain:</span>
            <span class="hljs-attr">region:</span> <span class="hljs-string">"us-east-1"</span>
            <span class="hljs-attr">service_name:</span> <span class="hljs-string">"lambda"</span>
</code></pre>
<p>There are other AWS security configuration options available, including using assume role. The full documentation for subgraph authentication <a target="_blank" href="https://www.apollographql.com/docs/graphos/routing/security/subgraph-authentication">is available here</a>. </p>
<h3 id="heading-step-9-build-the-connector">Step 9: Build the connector</h3>
<p>Now that we have configured the environment variables and authentication information, we are ready to build the connector. Open the <code>connector.graphql</code> file and erase the contents. Next, copy the following extend schema:</p>
<pre><code class="lang-graphql">extend <span class="hljs-keyword">schema</span>
  <span class="hljs-meta">@link</span>(
    <span class="hljs-symbol">url:</span> <span class="hljs-string">"https://specs.apollo.dev/federation/v2.10"</span>
    <span class="hljs-symbol">import:</span> [<span class="hljs-string">"@key"</span>]
  )
  <span class="hljs-meta">@link</span>(
    <span class="hljs-symbol">url:</span> <span class="hljs-string">"https://specs.apollo.dev/connect/v0.1"</span>
    <span class="hljs-symbol">import:</span> [<span class="hljs-string">"@source"</span>, <span class="hljs-string">"@connect"</span>]
  )

  <span class="hljs-meta">@source</span>(
    <span class="hljs-symbol">name:</span> <span class="hljs-string">"lambda"</span>
    <span class="hljs-symbol">http:</span> { <span class="hljs-symbol">baseURL:</span> <span class="hljs-string">"https://lambda.us-east-1.amazonaws.com"</span> }
  )
</code></pre>
<p><strong>Extend schema</strong> is used to link the Apollo Connectors directives into the current schema. In this article we are defining the base URL of our lambda function. If your REST API has HTTP headers that apply to all references of this source, such as Content-Length restrictions, you can add them here in the @source declaration. Next, let’s define the Product schema:</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Product {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">name:</span> String
  <span class="hljs-symbol">description:</span> String
  <span class="hljs-symbol">image:</span> String
  <span class="hljs-symbol">price:</span> Price
    <span class="hljs-meta">@connect</span>(
      <span class="hljs-symbol">source:</span> <span class="hljs-string">"lambda"</span>
      <span class="hljs-symbol">http:</span> {
        <span class="hljs-symbol">POST:</span> <span class="hljs-string">"/2015-03-31/functions/product-price/invocations"</span>
        <span class="hljs-symbol">body:</span> <span class="hljs-string">""</span><span class="hljs-string">"
        product_id: $this.id
        "</span><span class="hljs-string">""</span>
      }
      <span class="hljs-symbol">selection:</span> <span class="hljs-string">""</span><span class="hljs-string">"
      amount: default_price
      isActive: is_active
      currency
      recurringInterval: recurring.interval -&gt; match(
        [0,"</span>ONE_TIME<span class="hljs-string">"],
        [1,"</span>DAILY<span class="hljs-string">"],
        [2,"</span>MONTHLY<span class="hljs-string">"],
        [3,"</span>ANNUALLY<span class="hljs-string">"],
      )
      recurringCount: recurring.interval_count
      "</span><span class="hljs-string">""</span>
    )
}
</code></pre>
<p>Notice our query Products has an @connect directive that defines, at a minimum, the source name. Here, you can add the HTTP-specific configuration you need for this field, such as Authorizations headers. In this scenario, since we only defined a baseUrl in the extend schema section, we need to put the specific URL for the InvokeFunction, which is <strong>/2015-03-31/functions/product-price/invocations</strong>.</p>
<p>The selection field allows you to transform and map values returned from the REST API using the mapping definition defined in the selection field. While a complete discussion of selection mapping is beyond the scope of this article, check out the documentation for a detailed look at <a target="_blank" href="https://www.apollographql.com/docs/graphos/schema-design/connectors/responses?utm_campaign=2025-03-20_mapping-graphql-responses-doc-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Mapping GraphQL Responses</a>.  Apollo <a target="_blank" href="https://www.apollographql.com/connectors-mapping-playground?utm_campaign=2025-03-20_connectors-mapping-playground-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">provides a free online tool</a> that makes building mappings intuitive and fast.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756290237/91d17c59-a2d0-4a22-8acf-1faec0c0f36f.jpeg" alt="connectors mapping playground" class="image--center mx-auto" width="1560" height="984" loading="lazy"></p>
<p>Next, let’s define the Price schema and products Query.</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> Price {
  <span class="hljs-symbol">amount:</span> Float
  <span class="hljs-symbol">isActive:</span> Boolean
  <span class="hljs-symbol">currency:</span> String
  <span class="hljs-symbol">recurringInterval:</span> RecurringInterval
  <span class="hljs-symbol">recurringCount:</span> Int
}
<span class="hljs-keyword">enum</span> RecurringInterval {
  ONE_TIME
  DAILY
  MONTHLY
  ANNUALLY
}

<span class="hljs-keyword">type</span> Query {
  <span class="hljs-symbol">products:</span> [Product]
    <span class="hljs-comment"># https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html</span>
    <span class="hljs-meta">@connect</span>(
      <span class="hljs-symbol">source:</span> <span class="hljs-string">"lambda"</span>
      <span class="hljs-symbol">http:</span> { <span class="hljs-symbol">POST:</span> <span class="hljs-string">"/2015-03-31/functions/products/invocations"</span> }
      <span class="hljs-symbol">selection:</span> <span class="hljs-string">""</span><span class="hljs-string">"
      $.body {
        id
        name
        description
        image
      }
      "</span><span class="hljs-string">""</span>
    )
}
</code></pre>
<p>Now we're ready to run our connector and issue queries to our graph! The complete configuration script is available <a target="_blank" href="https://gist.github.com/RWaltersMA/e44813a89c748e175d6997f659162b33.">at this Gist</a>.</p>
<h3 id="heading-step-10-run-the-connector">Step 10: Run the Connector</h3>
<p>If you're using VS Code, the repository includes a tasks.json file that adds a “rover dev” task, which launches Rover locally. </p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"version"</span>: <span class="hljs-string">"2.0.0"</span>,
    <span class="hljs-attr">"tasks"</span>: [{
        <span class="hljs-attr">"label"</span>: <span class="hljs-string">"rover dev"</span>,
        <span class="hljs-attr">"command"</span>: <span class="hljs-string">"rover"</span>, <span class="hljs-comment">// Could be any other shell command</span>
        <span class="hljs-attr">"args"</span>: [<span class="hljs-string">"dev"</span>, <span class="hljs-string">"--supergraph-config"</span>,<span class="hljs-string">"supergraph.yaml"</span>, <span class="hljs-string">"--router-config"</span>,<span class="hljs-string">"router.yaml"</span>],
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"shell"</span>,
        <span class="hljs-attr">"problemMatcher"</span>: [],
    }]
}
</code></pre>
<p> If you are not using VS Code, you can start your graph by executing <code>rover dev –supergraph-config supergraph.yaml –router-config router.yaml</code> from a terminal window.</p>
<p>If everything is configured correctly, you’ll see the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756354078/9fab875a-d064-4723-be91-8ca0d6243b59.jpeg" alt="running rover dev command " class="image--center mx-auto" width="1558" height="390" loading="lazy"></p>
<h2 id="heading-section-3-how-to-use-apollo-sandbox">Section 3: How to Use Apollo Sandbox</h2>
<p>The <code>rover dev</code> command you launched in the previous step configures a local Apollo Router instance for <a target="_blank" href="https://www.apollographql.com/docs/graphos/reference/router/configuration?utm_campaign=2025-03-20_router-configuration-doc-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp#-dev">development mode</a>. This mode makes it easy for developers to create, execute, and debug ad-hoc GraphQL queries using the Apollo Sandbox web portal. This portal is located at <a target="_blank" href="http://localhost:4000">http://localhost:4000</a> by default.</p>
<p>Launch the portal and click on the products field. This will populate the Operation pane with all the available fields in the schema. In the operation pane, you can modify and build your GraphQL query. Clicking the Run button (which displays the query name, Products, in our example) will execute the query and show the results in the Response panel, as illustrated in the figure above.</p>
<p>In this example, you can see that data has been returned from our AWS Lambda function. To confirm, you can view the query plan by selecting "Query Plan” from the Response drop-down menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756499055/822467d7-0694-423e-baef-450a8d0dd64e.jpeg" alt="query plan menu item" class="image--center mx-auto" width="590" height="566" loading="lazy"></p>
<p>The query plan illustrates the orchestration of our two AWS Lambda functions that fetch product and product price data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756540147/af32d615-029a-4f6f-a489-96ee9950e630.jpeg" alt="query plan" class="image--center mx-auto" width="1542" height="1010" loading="lazy"></p>
<p>A helpful debugging feature is the Connectors Debugger, available in the drop-down as shown in the previous figure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742756614481/c9b311e3-ab5d-4927-9457-e9a7d242fbdf.jpeg" alt="debugger showing request overview" class="image--center mx-auto" width="775" height="818" loading="lazy"></p>
<p>The Connection Debugger provides a comprehensive view of the HTTP request, including headers, body, response code, and the selection mapping used in the query. If you’re experiencing difficulties running queries, use this debugger – it will save you a lot of time.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this article, you learned how to: </p>
<ul>
<li><p>Set up AWS IAM User, Policies, and Lambda functions</p>
</li>
<li><p>Create an Apollo Connector to obtain data from an AWS Lambda function</p>
</li>
<li><p>Configure the Apollo Router </p>
</li>
<li><p>Execute and debug queries using Apollo Sandbox</p>
</li>
</ul>
<p>Integrating AWS Lambda with Apollo Connectors offers a simplified, resolver-free method for incorporating cloud functions into your GraphQL API. By utilizing Apollo Connectors, you can declaratively link REST-based Lambda functions to your supergraph while ensuring secure authentication with AWS SigV4.</p>
<p>You can learn more about Apollo Connectors from the following resources:</p>
<ol>
<li><p>Tutorial: <a target="_blank" href="https://www.apollographql.com/tutorials/connectors-intro-rest?utm_campaign=2025-03-20_connectors-intro-rest-odyssey-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">GraphQL meets REST, with Apollo Connectors</a></p>
</li>
<li><p>Blog: Discover how Apollo Connectors integrate with Apollo Federation through insights from Apollo's Founder &amp; CTO: <a target="_blank" href="https://www.apollographql.com/blog/api-orchestration-with-graphql?utm_campaign=2025-03-20_api-orchestration-with-graphql-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">REST API Orchestration with GraphQL</a>.</p>
</li>
<li><p>Blog: Delve into the engineering journey behind Apollo Connectors and the process of their creation: <a target="_blank" href="https://www.apollographql.com/blog/our-journey-to-apollo-connectors?utm_campaign=2025-03-20_our-journey-to-apollo-connectors-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Our Journey to Apollo Connectors</a></p>
</li>
<li><p>Webinar: <a target="_blank" href="https://www.apollographql.com/events/new-innovations-from-apollo-dont-miss-out?utm_campaign=2025-03-20_new-innovations-from-apollo-dont-miss-out-march2025awareness&amp;utm_medium=blog&amp;utm_source=freecodecamp">Apollo Connectors GA Launch Webinar</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
