<?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" version="2.0"><channel><title><![CDATA[Peterson Oaikhenah  -- Senior Software Engineer | Community leader | Cloud Engineer | Open Source]]></title><description><![CDATA[Motivated senior software engineer, open-source advocate & community leader 💻⚡️. Sharing valuable insights to help others scale 💡. Building strong communities]]></description><link>https://blog.nextwebb.tech</link><generator>RSS for Node</generator><lastBuildDate>Tue, 06 Oct 2026 16:09:21 GMT</lastBuildDate><atom:link href="https://blog.nextwebb.tech/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Turning Bad LLM Responses into Regression Checks with LLMCheck]]></title><description><![CDATA[Capture a bad response, turn it into a regression case, and check how the verdict is decided.
Imagine a support assistant promising an instant refund, even though the policy requires manager approval ]]></description><link>https://blog.nextwebb.tech/turning-bad-llm-responses-into-regression-checks-with-llmcheck</link><guid isPermaLink="true">https://blog.nextwebb.tech/turning-bad-llm-responses-into-regression-checks-with-llmcheck</guid><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Wed, 23 Sep 2026 17:54:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/2be11ed7-e5c3-42be-bcb4-3ee1827fbc0f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Capture a bad response, turn it into a regression case, and check how the verdict is decided.</em></p>
<p>Imagine a support assistant promising an instant refund, even though the policy requires manager approval and three to five business days for processing. That’s a response worth keeping as a test. 🤔</p>
<p>I wanted to keep a bad response as a test I could rerun after changing the application. LLMCheck is my Python project for capturing model calls, reviewing failures, and turning them into regression cases.</p>
<p>Let’s capture that response, edit the generated test criteria, run a new response through the test, and inspect the verdict together. 🧪 We’ll also try answers that could fool the checker. Those checks exposed a draft-generation defect and showed that low confidence does not prevent a passing result.</p>
<p>The walkthrough uses LLMCheck code with a made-up refund policy and scripted responses. It runs offline. Later, we compare a literal string checker with a recorded OpenAI evaluation of twelve examples from that policy.</p>
<p>The <strong>judge</strong> is the model or function that checks an answer against the case’s criteria. We will inspect its explanation and reported problems alongside each verdict.</p>
<p>You can <a href="https://llmcheck-demo.onrender.com">inspect the refund-policy cases in the explorer</a> or <a href="https://github.com/nextwebb/llmcheck/tree/main/examples/refund-policy">run the current refund-policy example</a>. The optional setup below reproduces the older implementation used in this walkthrough, including the defect discussed later.</p>
<h2>What LLMCheck Actually Is 🔍</h2>
<p>LLMCheck connects four operations: capture a call, review its output, save an approved case, and run the application against that case again. It stores captured records in SQLite and regression criteria in YAML. At the source revision used here, the instrumentation wraps the synchronous <code>client.chat.completions.create(...)</code> interface.</p>
<p>LLMCheck checks response text. You still need application tests to confirm that a database transaction committed, an advert was updated, or an external service accepted an action. It also does not provide production tracing or wrappers for every provider’s SDK.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/453b58e9-57dc-401f-a43b-036c26b97b32.png" alt="Whiteboard overview of capture, review, YAML, execution, evidence extraction and verdict." style="display:block;margin:0 auto" />

<p><em>Review the case, run the application, and inspect the judge’s reported violations. Python uses those violations to calculate the verdict.</em></p>
<p>The default judge receives the new output, approved context, and expected criteria, then returns lists of violations. Python passes the case when those lists are empty. A judge that misses a problem can therefore produce a false pass. We will look at that code below.</p>
<p>The repository also has an experimental feature for reusing reviewed knowledge in later calls. We will cover it after the regression walkthrough.</p>
<p>The offline walkthrough and its original figures use source commit <code>6d101ae90781b8dc06965f57313445f8878cf6d6</code>, version <code>0.3.0</code>. Inspect the <a href="https://github.com/nextwebb/llmcheck/tree/6d101ae90781b8dc06965f57313445f8878cf6d6">historical source</a> alongside the demo scripts. The release update and recorded live evaluation below cover later work. Use the pinned commit to reproduce the original examples, including the defect.</p>
<h3>Release update: what changed after the walkthrough</h3>
<p>The core is licensed under Apache 2.0. <a href="https://github.com/nextwebb/llmcheck/pull/3">PR #3</a>, merged as <code>70a6fde</code>, fixes the draft-generation defect examined below and hardens capture, dashboard rendering and installation. The merged revision passed 58 tests in the checkout and extracted source archive, with CI passing on Python 3.10, 3.11 and 3.12.</p>
<p>The <a href="https://github.com/nextwebb/llmcheck/tree/ce9116715b359b61197dc1c4931ab81a5129a4a6">earlier 0.4.0 candidate, <code>ce91167</code></a>, passed 53 local tests. In the fixed generator, correction text stays in the rubric; reviewers fill in the required and forbidden phrase lists.</p>
<p><a href="https://llmcheck-demo.onrender.com">Open the case explorer</a> to switch between the literal fixture and the recorded OpenAI run. Clicks display saved evidence and do not call a provider. <a href="https://github.com/nextwebb/llmcheck/tree/986a3118c4c4f6f35d995bb89a8479e32fef0925/examples/article">Inspect the archived experiment scripts</a> to reproduce the original 0.3.0 walkthrough, including its historical defect. The live run used the modified candidate before it was committed: its base Git revision and exact judge/script hashes are preserved in the <a href="https://github.com/nextwebb/llmcheck/blob/ce9116715b359b61197dc1c4931ab81a5129a4a6/src/llmcheck/demo_assets/live-evaluation.json">release evidence</a>.</p>
<h2>Why This Problem Matters to Me 💭</h2>
<p>I worked on a publishing data platform, operating and extending recommendation workflows across 37 configured production publishing sites.</p>
<p>That work included historical and incremental processing. Processing an existing dataset can follow a different path from handling the changes that arrive afterward, so both paths need validation. The Airflow work involved diagnostic runs, investigation with the engineering team, and recorded validation results.</p>
<p>I want the same details available when investigating a model response: the input, the environment, what we expected, and what happened.</p>
<p>I also worked on advertising systems, including Python agent workflows connected to Google Ads operations and backend APIs. An agent response was only one part of the request path. Context, credentials, database access, service boundaries, and returned data all influenced what happened next.</p>
<p>In those systems, checking the answer text alone would miss problems elsewhere in the request. The application might supply incomplete information to the model, or take an inefficient path to produce a reasonable answer. Investigating the response means checking those surrounding steps too.</p>
<p>Those projects inform how I approach testing. LLMCheck is an independent project, and the examples below use synthetic data.</p>
<h2>Prerequisites and What We Will Run 🧰</h2>
<p>To reproduce this walkthrough, you need Python 3.10 or later, Git and PyYAML. We will use two pinned checkouts: one containing the original experiment scripts, and one containing the 0.3.0 library used in the figures. For a first run with the current tool, use the refund-policy example linked above.</p>
<p>For the offline walkthrough, you do not need an OpenAI API key or the OpenAI Python package. The <code>offline_demo.py</code> script constructs a small object with the same chat-completions shape used by the wrapper. Its response is predetermined. An injected judge then checks predetermined strings.</p>
<p>We’ll start with fixed responses so we can test capture, storage, review and replay without making model requests. 💻 Then we’ll check where the literal judge gets the policy wrong.</p>
<p>Let’s get the example ready. Clone the public source into a fresh directory: 🛠️</p>
<pre><code class="language-bash">git clone https://github.com/nextwebb/llmcheck.git
cd llmcheck
git checkout --detach 986a3118c4c4f6f35d995bb89a8479e32fef0925
git worktree add --detach ../llmcheck-article-0.3.0 6d101ae90781b8dc06965f57313445f8878cf6d6
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'PyYAML&gt;=6.0'

LLMCHECK_SOURCE="$PWD/../llmcheck-article-0.3.0"
LLMCHECK_EXAMPLES="$PWD/examples/article"
LLMCHECK_OUTPUT="$(mktemp -d)"
</code></pre>
<p>The demo script’s <code>--repo</code> argument adds the checkout's <code>src</code> directory to Python's import path, so an editable package installation is not needed for that route.</p>
<p>To use the historical CLI and dashboard shown later, install that worktree in this environment:</p>
<pre><code class="language-bash">python -m pip install -e "$LLMCHECK_SOURCE"
llmcheck --help
</code></pre>
<p>The <a href="https://github.com/nextwebb/llmcheck/tree/986a3118c4c4f6f35d995bb89a8479e32fef0925/examples/article">archived example directory</a> contains three main scripts: <code>offline_demo.py</code> runs the example, <code>smoke_test.py</code> checks it, and <code>inspect_results.py</code> prints the saved results. Its <code>evidence</code> directory contains the twelve-answer experiment and confidence checks. These scripts call the LLMCheck library.</p>
<p>Run the following commands in the same terminal so the path variables remain available. <code>LLMCHECK_OUTPUT</code> is a fresh temporary directory; each new run needs an empty directory because the demo refuses to overwrite earlier results.</p>
<p>If you already know the implementation, skip ahead to the twelve-case experiment, confidence, and draft-polarity sections for the checker’s failure cases. 📌</p>
<h2>1. Define a Failure Before Writing a Detector 📝</h2>
<p>Our teaching policy has two required facts: refunds above $100 need manager approval, and processing takes three to five business days. The question is whether a $150 refund can arrive today. The scripted bad answer is <code>Your refund is instant.</code></p>
<p>The response has three problems: it omits approval, omits the processing window, and promises an immediate refund. A policy this small lets us check each problem directly.</p>
<p>Write those three requirements into the case. Instructions such as “be accurate” or “do not hallucinate” leave the reviewer to work out what counts as a correct answer each time.</p>
<p>Searching for <code>instant</code> can help you select runs for inspection, but it will also flag “The refund is not instant.” The reviewer or judge still needs to decide whether the response violates the policy.</p>
<p>The demo script flags this scripted failure directly. Later, we try substring matching as the judge and see which answers it gets wrong.</p>
<p>Let’s keep the first case small enough to inspect by hand: 📝</p>
<pre><code class="language-text">Question: Can I get a $150 refund today?
Policy: Approval required; processing takes 3-5 business days.
Observed response: Your refund is instant.
Failure: Missing required facts and an unsupported timing promise.
</code></pre>
<p>Save the question and policy with the response so the next person reviewing it can check the failure against the same requirements.</p>
<h2>2. Capture the Response Without Hiding the Test Double 📥</h2>
<p>The following excerpt shows the shape of the offline client. The full runnable version is in <a href="https://github.com/nextwebb/llmcheck/blob/986a3118c4c4f6f35d995bb89a8479e32fef0925/examples/article/offline_demo.py"><code>examples/article/offline_demo.py</code></a>.</p>
<pre><code class="language-python">from types import SimpleNamespace

class SyntheticCompletions:
    def create(self, **kwargs):
        return SimpleNamespace(
            choices=[SimpleNamespace(
                message=SimpleNamespace(
                    content="Your refund is instant."
                )
            )]
        )

client = SimpleNamespace(
    chat=SimpleNamespace(completions=SyntheticCompletions())
)
</code></pre>
<p>We will wrap this local client with LLMCheck’s instrumentation. This test double returns no token-usage metadata. The recorded latency measures its local function call.</p>
<p>Attach the wrapper and the context you want stored with the call:</p>
<pre><code class="language-python">from pathlib import Path
from llmcheck import instrument_openai, add_context, add_tags, flag

client = instrument_openai(
    client,
    storage_path=Path("demo-output/fixture.db"),
)

add_context([{
    "id": "synthetic-policy",
    "text": "Refunds above $100 require manager approval "
            "and take 3-5 business days.",
}])
add_tags({
    "workflow": "SYNTHETIC-POC-DEMO",
    "source": "scripted provider; no live model",
})
</code></pre>
<p><code>add_context</code> saves material alongside the next captured call. To give that policy to the model, your application must also include it in the request. Finding a policy in the captured context does not tell you whether the application sent it.</p>
<p>The demo script calls the client and flags the result. The wrapper saves the messages, user input, output, context, tags and available metadata in SQLite after the call succeeds. It captures completion calls, not the rest of the application.</p>
<p>LLMCheck’s optional approved-knowledge feature can prepend system messages before a call. Our fresh demo workspace has no approved knowledge entries. If you enable that feature in an existing workspace, check the stored entries when investigating changes to a request.</p>
<h2>3. Run the Offline Demo and Inspect the Results 🧪</h2>
<p>Run the demo using the paths set above:</p>
<pre><code class="language-bash">python "$LLMCHECK_EXAMPLES/offline_demo.py" \
  --repo "$LLMCHECK_SOURCE" \
  --output "$LLMCHECK_OUTPUT"
</code></pre>
<p>The command generates the captured record, draft, reviewed case, suite and results using LLMCheck’s wrapper, storage, generator, runner and parser. The script performs the review edit so each run produces the same case.</p>
<p>Start with three files:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>What to check</th>
</tr>
</thead>
<tbody><tr>
<td><code>captured-run.json</code></td>
<td>The saved question, policy and authored bad answer belong to the same run.</td>
</tr>
<tr>
<td><code>capture-cli.txt</code></td>
<td>The CLI view agrees with the saved record.</td>
</tr>
<tr>
<td><code>provenance.json</code></td>
<td>The source revision and execution mode match the experiment you intended.</td>
</tr>
</tbody></table>
<p>Inspect the saved CLI display and JSON:</p>
<pre><code class="language-bash">cat "$LLMCHECK_OUTPUT/capture-cli.txt"
python -m json.tool "$LLMCHECK_OUTPUT/captured-run.json"
</code></pre>
<p>Look for four things. The output should be the scripted instant-refund sentence. The context should contain the synthetic policy. The tags should identify the run as a demonstration. The flag reason should explain what was wrong, rather than merely say “bad output.”</p>
<p><code>provenance.json</code> records the source commit and execution mode. IDs and timestamps can change between runs, but the four verdicts should stay the same because the responses and injected judge are fixed.</p>
<p>Run the separate smoke check too:</p>
<pre><code class="language-bash">python "$LLMCHECK_EXAMPLES/smoke_test.py" \
  --repo "$LLMCHECK_SOURCE"
</code></pre>
<p>It creates an isolated temporary run and checks capture, review artifacts, expected outcomes, and overwrite refusal. The tests do not open your normal application database. The demo also blocks the Python TCP connection entry points it uses as an accidental-network-call guard. That guard is not an operating-system sandbox for arbitrary untrusted code.</p>
<h2>4. Review the Generated YAML as a Specification 📋</h2>
<p>Before we approve the generated YAML, let’s read it as a test specification. 📋 Does it describe what a correct future answer must say? A correction written for one bad answer can make a poor test for other answers.</p>
<p>In this revision, the generator places the correction text into <code>must_include</code> and extracts double-quoted phrases into <code>must_not_claim</code>. Review both lists: this rule can misinterpret your feedback, as we will see in the draft-polarity example.</p>
<p>The demo’s scripted review replaces the broad correction with separate requirements:</p>
<pre><code class="language-yaml">expected:
  must_include:
    - manager approval
    - 3-5 business days
  must_not_claim:
    - instant
judge:
  type: rubric
  pass_if: &gt;-
    Require manager approval and 3-5 business days;
    do not claim "instant".
</code></pre>
<p>This is an excerpt from the reviewed case, not a complete suite file. The demo script also saves identifiers, inputs, context, and metadata. Keeping the generated draft and reviewed version separately lets you see what the script changed.</p>
<p>The offline judge searches for these exact substrings. The default model judge receives the same fields in its prompt, together with the context and rubric, and interprets their meaning.</p>
<p>When reviewing a case, allow correct ways to express the policy. A correct paraphrase may omit the exact phrase <code>manager approval</code> while still saying that a manager must approve the refund. Conversely, a sentence can include both required phrases and negate the policy. We will test both cases shortly.</p>
<p>In a separately configured application workspace, this command reviews the latest capture using that workspace’s <code>llmcheck.yaml</code> and <code>llmcheck_suite.yaml</code>:</p>
<pre><code class="language-bash">llmcheck review --latest
</code></pre>
<p>The CLI offers approval, editing, or rejection. Saving valid YAML through the Edit option also approves the case and appends it to the suite; there is no second confirmation. The demo script has already performed its review in code, so you do not need this command to complete the walkthrough.</p>
<p>If an output starts failing, compare it with the policy before editing the case. You may need to fix the application, update the case for a policy change, or correct an expectation that was wrong from the start.</p>
<h2>5. Understand What Replay Actually Executes 🔄</h2>
<p>The suite identifies a Python function using a module-and-function path:</p>
<pre><code class="language-yaml">runner:
  type: python
  callable: fixture_app:answer_user
</code></pre>
<p>Here’s the small test application we’ll use to follow the replay: 🔄</p>
<pre><code class="language-python">OUTPUT = ""

def answer_user(query):
    return OUTPUT
</code></pre>
<p>For each scenario, the demo script changes <code>OUTPUT</code>, then invokes the real suite runner. The runner imports the configured function and calls it with the case's inputs as keyword arguments. It evaluates the returned output rather than regrading the archived capture.</p>
<p>In a real integration, that function would call your current application. It could run retrieval, construct messages, and invoke a model. Those operations are your runner's responsibility. The saved case does not automatically restore an old index, database snapshot, tool response, or complete conversation.</p>
<p>To connect your application, expose its answer through an adapter with the same keyword parameter:</p>
<pre><code class="language-python"># Illustrative adapter: replace this import with your application's API.
from my_app import generate_answer

def answer_user(query):
    return generate_answer(query)
</code></pre>
<p>Save it as <code>application_adapter.py</code> beside the suite and make your application package importable in that environment. Change the suite callable to <code>application_adapter:answer_user</code>. The saved case supplies <code>query</code>, so that parameter name must match. This example assumes a synchronous function returning answer text. The <code>my_app</code> import is a placeholder, and this integration pattern was not executed in the offline experiment.</p>
<p>Call your adapter directly with the case's question before running the suite. Inspect its returned text and verify that retrieval and external actions use a controlled test environment. A callable that works in your web server may depend on request state or credentials that the suite process does not have.</p>
<p>Replay reruns the function with the saved inputs. If its answer depends on a catalogue or policy version, your runner must load the version you intend to test.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/5edf9a79-569a-46c0-bb33-866a2b3476cb.png" alt="Integrated explanation of reviewed criteria, injected judge payload and actual verdict rule." style="display:block;margin:0 auto" />

<p><em>The scripted passing response produces empty violation lists. The runner uses those lists to return a pass.</em></p>
<p>The demo script runs four scenarios to check that responses reach the judge and produce the expected verdicts:</p>
<table>
<thead>
<tr>
<th>Scripted response</th>
<th>Observed verdict</th>
<th>Why the injected judge returns it</th>
</tr>
</thead>
<tbody><tr>
<td>Instant refund</td>
<td>Fail</td>
<td>Required phrases absent; forbidden phrase present</td>
</tr>
<tr>
<td>Policy stated directly</td>
<td>Pass</td>
<td>Required phrases present; forbidden phrase absent</td>
</tr>
<tr>
<td>Approval without timing</td>
<td>Fail</td>
<td>Processing window absent</td>
</tr>
<tr>
<td>Policy plus an instant promise</td>
<td>Fail</td>
<td>Forbidden phrase present</td>
</tr>
</tbody></table>
<p>You can inspect those decisions without opening the full JSON. Use <code>inspect_results.py</code> to print the saved verdicts:</p>
<pre><code class="language-bash">python "$LLMCHECK_EXAMPLES/inspect_results.py" \
  "$LLMCHECK_OUTPUT/replay-results.json"
</code></pre>
<p>The demo run produces the following verdicts:</p>
<pre><code class="language-text">FAIL | Incorrect scripted response
  Missing: manager approval; 3-5 business days
  Forbidden: instant
  Reason: Synthetic fixture judge: literal matching only; no semantic grounding assessment.
PASS | Corrected scripted response
  Reason: Synthetic fixture judge: literal matching only; no semantic grounding assessment.
FAIL | Missing timing requirement
  Missing: 3-5 business days
  Reason: Synthetic fixture judge: literal matching only; no semantic grounding assessment.
FAIL | Contradictory promise
  Forbidden: instant
  Reason: Synthetic fixture judge: literal matching only; no semantic grounding assessment.
Stored synthetic results only; no application or judge was executed.
</code></pre>
<p>The reason is fixed across these scenarios; the missing and forbidden lists show why each case failed. This inspection command reads saved evidence; rerun <code>offline_demo.py</code> with a new output directory to execute the workflow again.</p>
<p>All four results matched the expected verdicts for these scripted responses. This checks the capture-to-replay path; the twelve-case experiment below tests where the literal judge breaks down.</p>
<h2>6. Trace the Boundary Between Judge and Verdict 🔎</h2>
<p>The default judge is asked to return a JSON object with three violation lists, a reason, and a confidence category:</p>
<pre><code class="language-json">{
  "missing_requirements": [],
  "forbidden_claims_found": [],
  "unsupported_claims": [],
  "reason": "The required facts are present.",
  "confidence": "high"
}
</code></pre>
<p>That object is an illustrative payload shape. In the offline demo, a test function supplies the JSON. In the default path, an OpenAI model supplies it. Both paths use the same response parser.</p>
<p>The parser requires lists of strings, a string explanation, and a confidence value of <code>high</code>, <code>medium</code>, or <code>low</code>. It rejects responses with missing fields or incorrect types.</p>
<p>The aggregation rule then asks whether any violation list is nonempty:</p>
<pre><code class="language-python">violations = (
    judge_result.missing_requirements
    or judge_result.forbidden_claims_found
    or judge_result.unsupported_claims
)
passed = not bool(violations)
</code></pre>
<p>A reported violation fails the case. Empty lists pass it. The suite passes when all its cases pass.</p>
<p>Here’s the catch. 🤔 If the judge overlooks a contradiction and returns empty lists, this rule still passes the case. The Python code does not check the policy again.</p>
<h2>7. Case Study: Twelve Answers That Challenge the Demo Judge 🧩</h2>
<p>The first four cases checked the workflow. I then used twelve cases to test how the literal judge handled paraphrases, negation, contradictions and extra promises. Each case had a policy label assigned before the run.</p>
<p>The policy for this experiment also rules out guaranteed approval and a promised cash bonus. The judge still checks two required substrings and one forbidden substring, and always returns an empty unsupported-claims list.</p>
<p>For now, we’re testing the injected literal judge. We’ll try the default model judge in section 12. 🧪</p>
<p>The categories were literal compliant answers, compliant paraphrases, compliant negations, missing required facts, contradictions containing the expected words, and unsupported extra promises. There were two cases in each category.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/162ac730-cf2e-4dfc-aa31-8eaeca9c49eb.png" alt="Category comparison with exact counts, false passes, false failures and scope note." style="display:block;margin:0 auto" />

<p><em>Four decisions agreed with the predefined labels and eight disagreed across these twelve selected cases.</em></p>
<p>Consider a paraphrase: “For a refund over $100, a manager must approve it, and processing takes three to five business days.” The policy meaning is preserved. The literal checker fails it because neither required phrase appears in the exact expected form.</p>
<p>Now consider negation: “Refunds above $100 require manager approval and take 3-5 business days. They are not instant.” The forbidden substring is present, so the checker fails a response that explicitly denies the prohibited promise.</p>
<p>A contradiction gets through: “Manager approval is not required for refunds above $100; processing takes 3-5 business days.” Both required strings appear, and the forbidden one does not. The checker passes a response that reverses the approval policy.</p>
<p>The judge also passes responses that state the required facts and then guarantee approval or promise a $25 cash bonus. Its unsupported-claims list is always empty, so neither promise affects the verdict.</p>
<p>The result is four false passes, four false failures, two correct passes, and two correct failures. The labels come from the refund policy defined for this experiment.</p>
<h2>8. Case Study: A Low-Confidence Result Still Passes 🤔</h2>
<p>What happens when the judge isn’t confident? 🤔 It returns a confidence field, but this revision does not use it to decide pass or fail.</p>
<p>The next probe passed four injected judge responses through the actual suite runner and parser. High confidence with empty violation arrays passed. Low confidence with empty arrays also passed. A reported violation failed. Malformed JSON produced an execution error.</p>
<p>In the reported-violation probe, the injected judge claimed a required phrase was missing even though the application output contained it. The case failed because the verdict logic uses the judge's violation lists without independently checking the answer.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/236a2e42-5c7b-46a2-99fb-581ed3a8e8f5.png" alt="Current confidence handling beside a proposed review policy." style="display:block;margin:0 auto" />

<p><em>The left side shows the probe results. The right side shows a proposed review state, which is not implemented.</em></p>
<p>I would add a review state alongside pass, fail and error. That would let a low-confidence response wait for review even when its violation lists are empty.</p>
<p>The values <code>high</code>, <code>medium</code>, and <code>low</code> are labels, not measured probabilities. We have not checked whether this judge's high-confidence verdicts are more often correct than its low-confidence ones, so the labels alone cannot tell us how dependable that review rule would be.</p>
<h2>9. Case Study: A Draft That Contradicts Its Correction ✏️</h2>
<p>The 0.3.0 generator produced a contradictory draft from this correction. The merged implementation fixes the defect, but the old output shows why draft review matters:</p>
<pre><code class="language-text">Include "manager approval".
</code></pre>
<p>Now look at the generated criteria. 🔍 They included the full correction as a required entry and extracted the quoted phrase as forbidden:</p>
<pre><code class="language-yaml">expected:
  must_include:
    - Include "manager approval".
  must_not_claim:
    - manager approval
</code></pre>
<p>The draft requires <code>manager approval</code> while also forbidding it. The 0.3.0 extraction code treated double-quoted text as forbidden without checking whether the correction asked to include it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/1a4a96cb-2179-425f-9f40-b8b2fb87d6d1.png" alt="Actual contradictory draft and a proposed manual correction." style="display:block;margin:0 auto" />

<p><em>The synthetic record reproduced the 0.3.0 defect. The repaired criteria illustrate a manual edit; the merged code fix works differently, as described below.</em></p>
<p>Both lists are valid YAML, so the parser accepts them. A reviewer could repair this case by requiring <code>manager approval</code>, clearing the forbidden list, and checking that the rubric agrees with those changes.</p>
<p>The merged fix stops extracting forbidden claims from quotation marks. It keeps the correction in the rubric and leaves both phrase lists empty for the reviewer.</p>
<h2>10. Keep the Application, Storage and Provider Boundaries Visible 🔐</h2>
<p>LLMCheck stores records locally. Its default judge sends evaluation material to OpenAI, and your application runner may make its own external calls.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/d220d7d9-c0bc-4f61-8789-7c7e9cbd83e0.png" alt="Whiteboard view of local storage and external evaluation boundaries." style="display:block;margin:0 auto" />

<p><em>The offline demo supplies fixed responses. The normal judge path crosses the provider boundary with inputs, context, output, criteria and rubric.</em></p>
<p>For the refund case, the judge needs the policy and the answer. It does not need a customer credential or an unrelated internal note. Choose what to capture with the later evaluation request in mind.</p>
<p>Keep a stable identifier and policy version with the context, along with the application and judge configuration. If the approval threshold changes from $100, those records let you check whether a failure comes from the application or an outdated case.</p>
<p>Replay executes your Python function. If that function sends email, changes a campaign, or writes to production, replay can repeat those actions. Use a sandboxed application path or substitute those actions during development evaluations.</p>
<p>The suite names the module and function to execute, and the runner makes the suite directory available during import. Inspect that code before running a suite from an unfamiliar source.</p>
<h2>11. Regression Cases and Reusable Knowledge Are Different Artifacts 📚</h2>
<p>The repository also has an experimental workflow that turns reviewed information into knowledge entries for later requests.</p>
<p>A regression case replays an input and checks the answer against reviewed expectations. An approved knowledge entry can supply information to later application requests.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/2616b22e-ff3a-4fe2-87b2-71b5902141f4.png" alt="Whiteboard fork separating regression checks from approved knowledge reuse." style="display:block;margin:0 auto" />

<p>The pilot classifies failures by cause: missing context, wrong retrieval targets, reasoning failure despite sufficient context, prompt or instruction failures, workflow failures, ambiguity, and false-positive detectors.</p>
<p>An answer that contradicts a supplied refund policy may not improve when given another copy of that policy. Retrieving the wrong document calls for checking retrieval constraints. An ambiguous policy needs clarification before it becomes reusable guidance.</p>
<p>At this revision, matching uses workflow scope, usage mode, and token overlap rather than semantic search. Certain approved modes prepend planning guidance or runtime context as system messages before the original application messages. The run metadata records which entries were applied. Detection-only and review-assist modes do not inject knowledge.</p>
<p>After installing the historical CLI above, open the dashboard against the generated workspace:</p>
<pre><code class="language-bash">llmcheck pilot-dashboard \
  -c "$LLMCHECK_OUTPUT/llmcheck.yaml" \
  --host 127.0.0.1 --port 8765
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/f14eff2a-326e-4604-9806-bdf33f331c68.png" alt="Native LLMCheck pilot dashboard showing one synthetic review and no approved knowledge entries." style="display:block;margin:0 auto" />

<p><em>The dashboard from an earlier isolated run of the same synthetic demonstration at the pinned revision. Its run ID and timestamp differ from a new demo run. The review count and percentages come from fixture data.</em></p>
<p>The demo script supplies one synthetic review to inspect in the dashboard. I have not measured whether knowledge reuse reduces repeated failures. That would need repeated cases, a baseline without the reused knowledge, and a comparison of both corrected answers and new errors.</p>
<h2>12. Move to a Live Judge as a Separate Experiment 🌐</h2>
<p>On 23 September 2026, we ran a separate evaluation using the default OpenAI judge. The script sent the twelve refund-policy cases to <code>gpt-4o-mini-2024-07-18</code>, once each, with no retries. All twelve requests completed.</p>
<p>The recorded outcomes were <strong>five correct passes, six correct failures, zero false passes and one false failure</strong>. The rejected compliant response included the sentence “Do not expect an instant refund.” The judge reported the negated phrase as forbidden. The saved run includes that false failure.</p>
<p>The <a href="https://llmcheck-demo.onrender.com">public explorer</a> shows each saved response in its Recorded OpenAI run mode. Clicking through those results does not repeat the evaluation, and the server needs no provider key. These selected examples show how this configuration handled the refund policy; they are too narrow to estimate accuracy on other tasks.</p>
<p>To use the live judge with your application, both 0.3.0 and the merged implementation require an OpenAI API key with access to the configured judge model. This is separate from the offline walkthrough. The environment above has the 0.3.0 CLI installed; these commands do not upgrade it. Follow the <a href="https://github.com/nextwebb/llmcheck#install">repository installation instructions</a> in a separate environment to use the current implementation.</p>
<p>Set <code>OPENAI_API_KEY</code> in the environment used to run the suite. Keep the key out of case files and source code. Set <code>judge.model</code> in <code>llmcheck.yaml</code> to choose the model; the configuration defaults to <code>gpt-4o-mini</code> when that field is omitted.</p>
<p>Before using the CLI, prepare a configuration with a real <code>judge.model</code> and a suite pointing to your application adapter from section 5. The offline demo uses <code>unused-in-fixture-demo</code> as its model name, and its saved fixture function starts with an empty response. Adding an API key does not turn those files into a live application test.</p>
<p>With your configuration saved as <code>llmcheck.yaml</code> and your suite as <code>llmcheck_suite.yaml</code>, run:</p>
<pre><code class="language-bash">llmcheck run-suite -c llmcheck.yaml --suite llmcheck_suite.yaml
</code></pre>
<p>This command calls OpenAI through the default judge, even when your application function returns a fixed string. The offline demo supplies its own judge through the Python API; the CLI has no equivalent offline switch.</p>
<p>For a follow-up, I would add cases that were not used to adjust the prompt, covering paraphrases, negation, unsupported additions and contradictions. The twelve cases and their authored labels stayed fixed during this run.</p>
<p>Save the judge model, prompt version, case-set version, source commit, full payload and returned JSON so you can compare runs. Repeating requests under the same configuration would show whether the verdicts vary. The source uses temperature zero, which does not guarantee identical remote responses.</p>
<p>Keep false passes and false failures separate. In this run, the one disagreement rejected a compliant answer. A false pass on “manager approval is not required” would have a different consequence: accepting an answer that reverses the policy.</p>
<p>Latency, token usage and cost were not measured in this run. The judge transport returns response content without token-usage data. To compare costs or timings, instrument those values separately and use the same case set for each configuration.</p>
<h2>13. Make the CI Contract Explicit ⚙️</h2>
<p>The CLI returns zero when all cases pass, one when at least one case fails, and two for caught configuration or suite-execution errors. Code two covers handled import, runner and judge failures; uncaught exceptions are not universally mapped to it.</p>
<p>A case that fails because the answer promises an instant refund needs an application or expectation review. A missing module or malformed judge response needs an execution fix. Both may block a release, but the saved error should tell you which problem to investigate.</p>
<p>Parser and aggregation tests use controlled inputs, so they can run on every change without a provider call. Choose when to run the live judge based on its cost and how often the application or evaluation criteria change.</p>
<p>Review cases before adding them to a release gate. “Refunds are instant” and “Do not expect an instant refund” contain the same forbidden word, but test different meanings, so keep both. Repeated copies of the same promise add less coverage. When the policy changes, update the affected expectations and record why.</p>
<p>Save the application output, judge payload, reason and configuration for failures. Those files let you see whether the judge rejected a negation or found an actual promise.</p>
<p>Something didn’t behave as expected? Here’s where to start looking for the failures we covered: 🧰</p>
<table>
<thead>
<tr>
<th>Observation</th>
<th>Inspect next</th>
<th>Next action</th>
</tr>
</thead>
<tbody><tr>
<td>Stored policy was ignored</td>
<td><code>messages</code> and <code>context</code> in <code>captured-run.json</code></td>
<td>Check whether the application actually sent the policy. Recording context does not insert it into the prompt.</td>
</tr>
<tr>
<td>A case seems impossible to satisfy</td>
<td><code>generated-draft.yaml</code> and <code>reviewed-case.yaml</code></td>
<td>Look for a phrase required and forbidden at once. Review both the lists and rubric.</td>
</tr>
<tr>
<td>A paraphrase fails or a contradiction passes</td>
<td>Judge implementation and <code>replay-results.json</code></td>
<td>Check whether this was the literal fixture judge. Preserve the counterexample before changing criteria.</td>
</tr>
<tr>
<td>Low confidence still passes</td>
<td>Violation arrays and <code>confidence</code></td>
<td>Empty arrays explain the current verdict. A review state would require a separate policy change.</td>
</tr>
<tr>
<td>Replay errors before a verdict</td>
<td>Callable path, input names and reported error</td>
<td>Separate an import or runner failure from a malformed judge response. Neither is evidence that the answer violated policy.</td>
</tr>
<tr>
<td>The demo refuses a second run</td>
<td><code>--output</code> directory</td>
<td>Choose a new empty directory to preserve the earlier evidence.</td>
</tr>
</tbody></table>
<h2>14. What Passed, What Failed, and What Remains Open 📌</h2>
<p>The offline run saved the capture, generated and revised the case, and executed the four replay scenarios. All four verdicts matched their expected results. The separate smoke test also confirmed that a second run cannot overwrite the first run’s output directory.</p>
<p>The pinned 0.3.0 run passed 21 tests with provider fakes or mocks. The merged hardening revision passed 58 tests. Separately, the twelve OpenAI requests agreed with eleven authored labels and rejected one compliant answer containing a negation. The unit tests and model evaluation check different behavior.</p>
<h2>Wrapping Up 😊</h2>
<p>Start with the complete negation case when you try a different judge. Its answer includes the required approval and timing facts, followed by “Do not expect an instant refund.” The recorded judge rejected that compliant answer. Keep it alongside a response that actually promises an instant refund, and inspect the reasons for both verdicts.</p>
<p>Your turn. 😊 Add a failure from your own application, write down the expected behavior, and check it again after your next change. 🧪</p>
<p>Source: <a href="https://github.com/nextwebb/llmcheck">github.com/nextwebb/llmcheck</a>. To try the current tool, <a href="https://github.com/nextwebb/llmcheck/tree/main/examples/refund-policy">run the refund-policy example</a>. It captures a scripted response, saves a reviewed case and prints the replay results.</p>
<p>Have feedback or a case you want to discuss? Leave a comment 💬 or reach me on <a href="https://github.com/nextwebb">GitHub</a>, <a href="https://twitter.com/Iam_nextwebb">Twitter/X</a>, or <a href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>. If this helped, give it a thumbs up 👍 and share it with someone testing their own LLM application.</p>
]]></content:encoded></item><item><title><![CDATA[Automating Job Applications with Claude Code: A Hands-On Guide to career-agent]]></title><description><![CDATA[Hey there!
If you have applied to many engineering roles in a short period, you already know the routine.
You find a role that looks right. You read the job description. You adjust your CV. You write ]]></description><link>https://blog.nextwebb.tech/automating-job-applications-claude-code-career-agent</link><guid isPermaLink="true">https://blog.nextwebb.tech/automating-job-applications-claude-code-career-agent</guid><category><![CDATA[claude-code]]></category><category><![CDATA[automation]]></category><category><![CDATA[Career]]></category><category><![CDATA[Python]]></category><category><![CDATA[devtools]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Wed, 17 Jun 2026 13:30:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5e87f3322fcec10052c0fb74/3df2f5d2-7bf6-4061-9799-371ec6fbbc6c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey there!</p>
<p>If you have applied to many engineering roles in a short period, you already know the routine.</p>
<p>You find a role that looks right. You read the job description. You adjust your CV. You write a cover letter that should sound specific, not generic. You upload a PDF. Then you type the same name, phone number, location, LinkedIn URL, GitHub URL, work authorization answer, and application note into another ATS form.</p>
<p>Then you do it again.</p>
<p>After a while, the work becomes less about judgment and more about repetition. That is the part I wanted to remove.</p>
<p>I built <strong>career-agent</strong> to handle the mechanical layer of job applications from inside Claude Code. It helps you build a local profile, source roles, scaffold role configs, generate tailored PDFs, fill supported ATS forms in Chrome, and track your pipeline.</p>
<p>It does not submit applications for you.</p>
<p>That boundary matters.</p>
<p>The plugin helps you prepare the application. You still review the CV, review the cover letter, answer sensitive questions yourself, and click Submit only when you are ready.</p>
<p>In this guide, I will walk through how to install it, what each command does, where the files live, what I tested, and where you should still slow down.</p>
<h2>What career-agent Actually Is</h2>
<p><strong>career-agent</strong> is a Claude Code plugin for job application workflows.</p>
<p>It gives you six slash commands:</p>
<pre><code class="language-text">/setup-profile
/source
/new-role
/generate-cv
/apply
/track
</code></pre>
<p>The plugin is not a SaaS app. It is not a hosted job board. It is not a browser extension by itself.</p>
<p>It runs locally through Claude Code and writes files into your workspace:</p>
<pre><code class="language-text">profile.json
roles/
generated/
tracker.json
</code></pre>
<p>Those files are gitignored by default because they can contain personal data, role notes, generated CVs, cover letters, and application history.</p>
<p>That local file model is intentional. I wanted the workflow to be inspectable. If Claude generates a role config, you can open the JSON file. If the CV does not sound right, you can inspect the profile data and regenerate. If an application status changes, you can see it in <code>tracker.json</code>.</p>
<p>This is the high-level command map:</p>
<ul>
<li><code>/setup-profile</code> builds <code>profile.json</code> from your CV, LinkedIn export, or pasted profile text</li>
<li><code>/source</code> helps find and shortlist roles based on your profile and search terms</li>
<li><code>/new-role</code> creates a structured <code>roles/&lt;role_id&gt;.json</code> file from a job URL</li>
<li><code>/generate-cv</code> generates a tailored CV and cover letter PDF</li>
<li><code>/apply</code> fills supported ATS forms in Chrome, then stops for your review</li>
<li><code>/track</code> updates the local application pipeline</li>
</ul>
<p>The supported ATS platforms today are Greenhouse, Lever, and Workable.</p>
<p>That does not mean every page variant will work perfectly. ATS pages differ by company, region, custom fields, embedded forms, and hidden inputs. Treat <code>/apply</code> as a form-filling draft, not as a final application.</p>
<p>The strongest way to think about career-agent is this:</p>
<pre><code class="language-text">Claude handles the repeated structure.
You handle the judgment.
</code></pre>
<p>That means you still decide which roles are worth applying to, what story the cover letter should tell, what sponsorship or location answers are accurate, and when to submit.</p>
<h2>Why I Built It</h2>
<p>I apply to a lot of roles.</p>
<p>When you apply at that volume, the problem is not writing one good CV. The problem is maintaining quality while repeating the same application mechanics many times.</p>
<p>The work breaks into two parts:</p>
<ol>
<li><strong>Judgment work</strong>: choosing roles, deciding what experience to highlight, checking whether the company is a fit, and making sure the final application is honest.</li>
<li><strong>Mechanical work</strong>: copying fields, generating PDFs, formatting cover letters, creating folders, tracking status, and filling the same forms again.</li>
</ol>
<p>Most of the stress comes from the mechanical work leaking into the judgment work.</p>
<p>You start with good intent, but after the twentieth application, small mistakes become easier. Maybe the summary still mentions the previous company. Maybe the cover letter has a generic line. Maybe the tracker is missing a status. Maybe you cannot remember whether you already applied to that role.</p>
<p>career-agent is my attempt to separate those layers.</p>
<p>I want Claude to help with the repeatable workflow, but I do not want Claude to own the irreversible action.</p>
<p>That is why the tool stops before Submit.</p>
<h2>What Was Actually Tested</h2>
<p>For this article, I used a mix of real local runs and documented command behavior.</p>
<p>The install flow was run in a disposable directory with:</p>
<pre><code class="language-bash">npx @nextwebb/career-agent
</code></pre>
<p>That verified Python detection, <code>reportlab</code>, Claude Code CLI detection, and <code>profile.json</code> creation.</p>
<p>The generation and tracker screenshots were also produced from a clean temporary workspace using example profile and role data. That means the screenshots do not expose real personal data.</p>
<p>The article screenshots show:</p>
<ul>
<li>A real <code>npx @nextwebb/career-agent</code> install run</li>
<li>A role config list from a clean example workspace</li>
<li>A real PDF generation command using the example role</li>
<li>A tracker command adding and marking an example role as applied</li>
</ul>
<p>What I did not do for this article:</p>
<ul>
<li>I did not run <code>/apply</code> against a live company application form while writing this article</li>
<li>I did not submit a real job application for the tutorial</li>
<li>I did not use real personal profile data in the screenshots</li>
</ul>
<p>That distinction matters because a tutorial should not pretend that a browser automation flow is safer or more universal than it is. The local generation and tracking flows are easy to verify. Live ATS automation depends on the page in front of you.</p>
<h2>Prerequisites</h2>
<p>Before installing career-agent, ensure you have these tools installed:</p>
<pre><code class="language-text">Node.js 18+
Python 3.10+
Claude Code CLI
Claude in Chrome extension, required for /apply
</code></pre>
<p>Check your local versions:</p>
<pre><code class="language-bash">node --version
python3 --version
claude --version
</code></pre>
<p>Node is required because the setup entry point runs through <code>npx</code>.</p>
<p>Python is required because the PDF generation uses <code>reportlab</code>.</p>
<p>Claude Code is required because this is a Claude Code plugin. The slash commands live inside Claude Code, not inside a standalone web app.</p>
<p>Claude in Chrome is required only for <code>/apply</code>. You can set up your profile, create role configs, generate PDFs, and track applications without touching Chrome. But if you want Claude to fill ATS forms, the Chrome extension needs to be connected to Claude Code.</p>
<p>Note that being signed in to Claude.ai is not enough. Check the Claude in Chrome extension connection before depending on <code>/apply</code>. 📌</p>
<h2>1. Install career-agent</h2>
<p>Create a workspace for your job application workflow:</p>
<pre><code class="language-bash">mkdir ~/career-agent-workspace
cd ~/career-agent-workspace
</code></pre>
<p>Then run the npm setup command:</p>
<pre><code class="language-bash">npx @nextwebb/career-agent
</code></pre>
<p>npm package: <a href="https://www.npmjs.com/package/@nextwebb/career-agent">@nextwebb/career-agent</a> 📦</p>
<p>The setup script checks your local environment and creates a starter <code>profile.json</code> in the current directory.</p>
<p>Specifically, <code>bin/setup.js</code> checks:</p>
<ul>
<li>Python 3.10 or newer</li>
<li><code>reportlab</code></li>
<li>Claude Code CLI on your <code>PATH</code></li>
<li>Whether <code>profile.json</code> already exists</li>
</ul>
<p>If <code>reportlab</code> is missing, the setup tries to install it with pip. If Claude Code is missing, it stops and tells you to install it first.</p>
<p>This matters because the plugin is not useful if the PDF generator cannot run or if Claude Code cannot see the plugin commands.</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781695097/career-agent/b1-terminal.png" alt="Terminal: career-agent setup output confirming Python 3, reportlab, and Claude CLI are ready" style="display:block;margin:0 auto" />

<p>Here is the same install flow from a disposable terminal run:</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781699809/career-agent/terminal-install-npx.png" alt="Terminal: npx career-agent install checks Python, reportlab, Claude CLI, and creates profile.json" style="display:block;margin:0 auto" />

<p>After the npm setup, register the plugin source:</p>
<pre><code class="language-bash">claude plugin marketplace add nextwebb/career-agent
</code></pre>
<p>That command registers the GitHub repo as a Claude Code plugin source. I am not presenting career-agent as an official Anthropic marketplace listing. The evidence-backed claim here is narrower: the installer and manual command use the GitHub-hosted plugin source.</p>
<p>Then install the plugin:</p>
<pre><code class="language-bash">claude plugin install career-agent
</code></pre>
<p>Restart Claude Code after installing.</p>
<p>Open a new Claude Code session, type <code>/</code>, and check that the commands appear.</p>
<p>You should see:</p>
<pre><code class="language-text">/setup-profile
/source
/new-role
/generate-cv
/apply
/track
</code></pre>
<p>If they do not appear, check Claude Code plugin settings and confirm that the plugin source was added correctly. ✅</p>
<h2>2. Set Up Your Profile</h2>
<p>The profile is the center of the workflow.</p>
<p>Run:</p>
<pre><code class="language-text">/setup-profile
</code></pre>
<p>Claude will ask for your CV, LinkedIn export, or pasted profile text. It then creates <code>profile.json</code>.</p>
<p>The profile file is more than a contact card. It stores your name, location, links, experience, education, skills, summaries, and CV variants.</p>
<p>The goal is to give Claude a structured source of truth, so every role-specific output starts from the same base.</p>
<p>A simplified profile looks like this:</p>
<pre><code class="language-json">{
  "name": {
    "first": "Jane",
    "last": "Doe"
  },
  "email": "jane@example.com",
  "location": "Berlin, Germany",
  "headline": "Senior Backend Engineer",
  "summary": "Backend engineer with experience building APIs and distributed systems.",
  "links": {
    "github": "https://github.com/janedoe",
    "linkedin": "https://www.linkedin.com/in/janedoe"
  },
  "experience": [],
  "education": [],
  "skills": []
}
</code></pre>
<p>The actual schema is larger because it supports variants and per-role bullet sets.</p>
<p>career-agent uses three audience variants:</p>
<ul>
<li><strong>A</strong>: AI, LLM, and evaluation roles</li>
<li><strong>B</strong>: data platform and pipeline roles</li>
<li><strong>C</strong>: senior backend and API roles</li>
</ul>
<p>That is useful because the same experience can be framed differently depending on the role.</p>
<p>For example, a platform migration can matter to backend teams because of reliability and APIs. The same migration can matter to data teams because of pipeline stability and data quality. The facts stay the same, but the emphasis changes.</p>
<p>Review <code>profile.json</code> after generation.</p>
<p>Do not skip this.</p>
<p>PDF extraction is useful, but it is not magic. Dates can be misread. Role titles can be merged. Links can be missed. A summary can sound too broad. You should fix titles, dates, links, and anything that does not sound like you.</p>
<p>This is also the right time to remove anything you do not want used in generated applications.</p>
<h2>3. Understand the Local Files</h2>
<p>Before running more commands, it helps to understand what the workspace will look like.</p>
<p>The important paths are:</p>
<pre><code class="language-text">profile.json
roles/
generated/
tracker.json
</code></pre>
<p><code>profile.json</code> is your base profile.</p>
<p><code>roles/</code> contains one JSON file per role.</p>
<p><code>generated/</code> contains the CV and cover letter PDFs.</p>
<p><code>tracker.json</code> stores application status and notes.</p>
<p>All of these are local files. They are also gitignored by default.</p>
<p>This design makes the workflow easy to inspect. If <code>/generate-cv</code> produces a weak PDF, you can inspect the profile and role config. If <code>/apply</code> fills a field badly, you can inspect <code>custom_answers</code> in the role config. If your pipeline feels messy, you can open <code>tracker.json</code>.</p>
<p>That is one of the main reasons I prefer this setup over a hidden workflow.</p>
<h2>4. Source Roles with /source</h2>
<p>If you already have a job URL, you can skip <code>/source</code> and start with <code>/new-role</code>.</p>
<p>If you want career-agent to help build a shortlist, run:</p>
<pre><code class="language-text">/source Germany backend
</code></pre>
<p>You can change the location and role type:</p>
<pre><code class="language-text">/source remote data platform
/source UK AI infrastructure
/source Netherlands backend
</code></pre>
<p>The sourcing command uses your profile as the baseline. It is meant to help find roles that match your background, not every role on the internet.</p>
<p>The expected output is a ranked list of roles with direct application URLs, fit notes, and signals around location, sponsorship, and role match.</p>
<p>Use this output as a starting point.</p>
<p>Still verify the important details yourself:</p>
<ul>
<li>Is the role currently open?</li>
<li>Is the application URL direct?</li>
<li>Does the location work for you?</li>
<li>Does the role mention sponsorship or relocation?</li>
<li>Is the role actually senior enough?</li>
<li>Does the company match what you want?</li>
</ul>
<p>I do not treat sourcing output as final truth. I treat it as a shortlist.</p>
<p>That distinction matters because job pages change, roles close, and sponsorship language is often vague.</p>
<h2>5. Create a Role Config with /new-role</h2>
<p>For a role you want to apply to, run:</p>
<pre><code class="language-text">/new-role &lt;job_url&gt;
</code></pre>
<p>This creates a role config under <code>roles/</code>.</p>
<p>The config includes:</p>
<ul>
<li>Role ID</li>
<li>Company name</li>
<li>Title</li>
<li>Location</li>
<li>Job URL</li>
<li>ATS platform</li>
<li>CV variant</li>
<li>Output prefix</li>
<li>Cover letter stubs</li>
<li>Custom answer fields</li>
</ul>
<p>A role config gives the rest of the workflow a stable reference. Instead of asking Claude to remember a job description across a long conversation, the role details live in a file.</p>
<p>A simplified role config looks like this:</p>
<pre><code class="language-json">{
  "role_id": "stripe_backend_2026",
  "company": "Stripe",
  "title": "Senior Backend Engineer",
  "location": "Remote, Europe",
  "url": "https://boards.greenhouse.io/example/jobs/123",
  "ats_platform": "greenhouse",
  "variant": "C",
  "custom_answers": {
    "hear_about_us": "LinkedIn",
    "visa_sponsorship": "",
    "work_authorization": "",
    "why_company": ""
  },
  "cover_letter": {
    "paragraphs": []
  }
}
</code></pre>
<p>This is where I slow down.</p>
<p>The scaffold is a starting point. It is not the final application.</p>
<p>Edit the cover letter stubs. Fill in <code>why_company</code>. Add accurate work authorization details. Add salary expectations only if you are comfortable doing so. If the job post includes a reading-check instruction, make sure it is represented correctly.</p>
<p>The screenshot below uses the example role config in a clean local workspace:</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781699811/career-agent/terminal-role-list.png" alt="Terminal: listing the local example role config before generating application files" style="display:block;margin:0 auto" />

<h2>6. Generate the CV and Cover Letter</h2>
<p>Once <code>profile.json</code> and <code>roles/&lt;role_id&gt;.json</code> are ready, run:</p>
<pre><code class="language-text">/generate-cv &lt;role_id&gt;
</code></pre>
<p>Under the hood, the generator reads your profile and the role config, then writes two PDFs into <code>generated/</code>.</p>
<p>The output format is:</p>
<pre><code class="language-text">generated/&lt;output_prefix&gt;_CV.pdf
generated/&lt;output_prefix&gt;_CoverLetter.pdf
</code></pre>
<p>The CV is intentionally ATS-safe:</p>
<ul>
<li>Single column</li>
<li>No tables</li>
<li>No images</li>
<li>No decorative layout tricks</li>
<li>Clickable links where relevant</li>
<li>Built with <code>reportlab</code></li>
</ul>
<p>That plain layout is not an accident.</p>
<p>Many ATS parsers still struggle with multi-column CVs, text boxes, icons, and complex PDF structure. A clean single-column PDF is less exciting visually, but it is easier for machines and recruiters to parse.</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781695098/career-agent/b2-cv.png" alt="Generated CV for demo role - tailored summary and matching skills highlighted" style="display:block;margin:0 auto" />

<p>Here is the terminal output from generating the example CV and cover letter:</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781699813/career-agent/terminal-generate-cv.png" alt="Terminal: generate_application creates a CV PDF and cover letter PDF for the example role" style="display:block;margin:0 auto" />

<p>Open both PDFs before applying.</p>
<p>Check:</p>
<ul>
<li>The summary</li>
<li>The role title</li>
<li>The ordering of experience</li>
<li>The skill list</li>
<li>The dates</li>
<li>The links</li>
<li>The cover letter tone</li>
<li>Any claims that sound stronger than your actual experience</li>
</ul>
<p>The goal is not to invent experience.</p>
<p>The goal is to present the most relevant parts of your real experience for that role.</p>
<p>If the PDF sounds too generic, go back to the role config. If it sounds inaccurate, go back to <code>profile.json</code>. The files are local because you are supposed to inspect and edit them.</p>
<h2>7. Fill the ATS Form with /apply</h2>
<p>For supported ATS pages, open the application in Chrome and run:</p>
<pre><code class="language-text">/apply &lt;role_id&gt;
</code></pre>
<p>career-agent reads the form, maps fields from your profile and role config, and fills what it can.</p>
<p>The supported platforms today are:</p>
<pre><code class="language-text">Greenhouse
Lever
Workable
</code></pre>
<p>The important boundary is this:</p>
<pre><code class="language-text">Claude fills fields.
You review every field.
You answer EEO and voluntary questions.
You click Submit.
</code></pre>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781695099/career-agent/apply-boundary.png" alt="Apply boundary: Claude fills fields, you review and answer EEO, you click Submit" style="display:block;margin:0 auto" />

<p>That boundary is deliberate.</p>
<p>Applications often include questions about gender, race, veteran status, disability status, sponsorship, work authorization, consent, relocation, and salary. Some of those are sensitive. Some of them have legal implications. Some of them need context that no tool should guess.</p>
<p>Claude should not answer those for you.</p>
<p>Claude should also not click Submit.</p>
<p>When <code>/apply</code> works well, it saves you from retyping the boring parts. It fills names, links, locations, uploads, and text fields that map cleanly to your local config.</p>
<p>But you still review.</p>
<p>Treat the filled page as a draft. ATS forms vary, even on the same platform. One company may use standard Greenhouse fields. Another may add custom dropdowns, hidden file inputs, or React comboboxes. Review everything before submitting. ❌</p>
<p>If a page is not Greenhouse, Lever, or Workable, you can still use the generated PDFs and tracker manually.</p>
<h2>8. Track the Application</h2>
<p>After submitting, update the tracker:</p>
<pre><code class="language-text">/track update &lt;role_id&gt; status=applied notes="Applied today"
</code></pre>
<p>You can also run:</p>
<pre><code class="language-text">/track
</code></pre>
<p><code>tracker.json</code> gives you one local place for statuses, notes, dates, and follow-ups.</p>
<p>That becomes useful quickly.</p>
<p>When you are applying to many roles, memory is not enough. You need to know which roles are drafts, which ones you applied to, which ones have phone screens, which ones are rejected, and which ones need follow-up.</p>
<p>The tracker supports statuses like:</p>
<pre><code class="language-text">draft
applied
screen
interview
offer
rejected
withdrawn
</code></pre>
<p>Here is a local tracker run using the example role:</p>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781699814/career-agent/terminal-track.png" alt="Terminal: tracker adds the role, marks it applied, and lists the current pipeline" style="display:block;margin:0 auto" />

<p>The tracker is not meant to replace a full CRM. It is meant to keep the application workflow from becoming scattered across memory, browser tabs, and random notes.</p>
<h2>How the Full Workflow Connects</h2>
<p>Here is the full flow:</p>
<pre><code class="language-text">1. Build profile.json with /setup-profile
2. Find or capture a role with /source
3. Create roles/&lt;id&gt;.json with /new-role
4. Generate PDFs with /generate-cv
5. Fill the form draft with /apply
6. Save status with /track
</code></pre>
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1781695099/career-agent/workflow-diagram.png" alt="Workflow: profile.json to roles config to generated PDFs to ATS form to tracker.json" style="display:block;margin:0 auto" />

<p>The value is not only one command.</p>
<p>The value is the chain.</p>
<p><code>/setup-profile</code> gives you a structured base.</p>
<p><code>/source</code> helps you find roles.</p>
<p><code>/new-role</code> turns a job URL into a local config.</p>
<p><code>/generate-cv</code> turns the profile and role config into PDFs.</p>
<p><code>/apply</code> fills the supported ATS form draft.</p>
<p><code>/track</code> keeps the status visible.</p>
<p>Because each stage writes or reads a local file, you can stop at any point and inspect the work.</p>
<p>That is important for trust.</p>
<p>If a tool hides the intermediate steps, you have to trust the whole output. If the tool gives you files, you can inspect the profile, inspect the role config, inspect the PDFs, and inspect the tracker.</p>
<p>That is the workflow I wanted.</p>
<h2>A Practical Example Flow</h2>
<p>Let us say I want to apply for a senior backend role in Europe.</p>
<p>I start with sourcing:</p>
<pre><code class="language-text">/source Germany backend
</code></pre>
<p>From that output, I pick a role that looks relevant.</p>
<p>Then I scaffold it:</p>
<pre><code class="language-text">/new-role https://boards.greenhouse.io/example/jobs/123456
</code></pre>
<p>I open the generated role config:</p>
<pre><code class="language-text">roles/example_backend_2026.json
</code></pre>
<p>I check the company name, role title, location, ATS platform, and custom answer fields.</p>
<p>Then I edit the cover letter paragraphs. I want them to reference something real: the company product, the infrastructure problem, the role requirements, or a public engineering post.</p>
<p>After that, I generate the PDFs:</p>
<pre><code class="language-text">/generate-cv example_backend_2026
</code></pre>
<p>I open the CV and cover letter.</p>
<p>If the summary sounds too generic, I edit the role config. If the experience bullets feel wrong, I update the profile. If the cover letter feels like filler, I rewrite it.</p>
<p>Only after that do I open the application page and run:</p>
<pre><code class="language-text">/apply example_backend_2026
</code></pre>
<p>Claude fills what it can.</p>
<p>I review the form, answer EEO fields myself, check the uploaded files, and submit manually.</p>
<p>Then I track it:</p>
<pre><code class="language-text">/track update example_backend_2026 status=applied notes="Applied after reviewing form"
</code></pre>
<p>That is the loop.</p>
<p>It is not magic. It is a structured local workflow.</p>
<h2>What I Like About This Approach</h2>
<p>The first thing I like is that the data stays local.</p>
<p>Job application data can be personal. It includes phone numbers, locations, employment history, immigration details, compensation expectations, and private notes. Keeping that in local gitignored files makes the workflow easier to reason about.</p>
<p>The second thing I like is that the tool has a natural review point.</p>
<p>The generated PDFs are not hidden. You open them. You read them. You decide whether they represent you well.</p>
<p>The third thing I like is that <code>/apply</code> has a human boundary.</p>
<p>I do not want a tool clicking Submit on my behalf. It is too easy to get one field wrong and send a bad application. It is also not appropriate for a tool to guess voluntary self-identification answers.</p>
<p>The fourth thing I like is that the workflow is composable.</p>
<p>You do not need to use every command every time. If you already have a role URL, start with <code>/new-role</code>. If you only want PDFs, stop after <code>/generate-cv</code>. If the ATS is unsupported, fill the form manually and still use <code>/track</code>.</p>
<p>That flexibility matters because real job applications are messy.</p>
<h2>How I Review an Application Before Submitting</h2>
<p>The review step is the part I do not automate.</p>
<p>That might sound strange after building a tool to automate job application work, but this is the boundary that makes the workflow usable for me.</p>
<p>When <code>/apply</code> fills a form, I treat the result as a draft. I do not treat it as a completed application. That mindset matters because even a good automation can misunderstand a field label, miss a hidden requirement, or map a custom question too broadly.</p>
<p>Here is the review checklist I use before I submit:</p>
<pre><code class="language-text">1. Confirm the company and role title
2. Confirm the uploaded CV and cover letter are the right files
3. Read every pre-filled text field
4. Check phone, email, location, and links
5. Check work authorization answers
6. Answer EEO or voluntary self-identification fields manually
7. Check custom screening questions
8. Check final consent boxes
9. Submit manually
10. Update tracker.json
</code></pre>
<p>The first check sounds obvious, but it catches mistakes.</p>
<p>If you are applying to several roles in one session, it is easy to generate files for one role and open a different tab. The file names help, but I still check the company name and title before moving forward.</p>
<p>The second check is the uploaded file check.</p>
<p>Many ATS forms show the uploaded filename after upload. I check that the CV and cover letter match the current role ID. If I see the wrong role name in the filename, I stop. It is better to catch that before Submit than to explain it later.</p>
<p>The third check is every text field.</p>
<p>I read the filled fields the way a recruiter would see them. Not as code. Not as JSON. As a person reading an application. If the answer feels too long, too vague, or too generic, I edit it in the form before submitting.</p>
<p>Work authorization deserves its own pause.</p>
<p>Do not let a tool guess this. If the role asks whether you need sponsorship now or in the future, answer based on your actual status and the wording of the form. If the wording is unclear, slow down and decide carefully. The tool can carry your configured answer, but it should not decide the answer for you.</p>
<p>EEO and voluntary self-identification fields are also yours to answer.</p>
<p>career-agent is designed not to fill those fields. That is intentional. Those fields can be sensitive, jurisdiction-specific, and personal. If you want to answer them, answer them yourself. If you prefer not to answer, choose that option yourself.</p>
<p>The final thing I check is consent.</p>
<p>Some applications include checkboxes for privacy policies, talent pools, communication consent, or data retention. I do not want automation accepting those for me without review. Read the labels. Decide what you agree to. Then submit.</p>
<p>After submission, I update the tracker immediately:</p>
<pre><code class="language-text">/track update &lt;role_id&gt; status=applied notes="Submitted after manual review"
</code></pre>
<p>That note looks small, but it helps later.</p>
<p>When you are applying at volume, the tracker becomes memory. It tells you what happened, when it happened, and what you need to follow up on.</p>
<h2>How I Use career-agent During an Application Week</h2>
<p>The best way to use this tool is not to rush from job URL to Submit.</p>
<p>For me, the better workflow is batching.</p>
<p>I separate the week into sourcing, preparation, generation, review, and submission. That keeps the work calm. It also reduces mistakes because I am not trying to research a company, rewrite a cover letter, generate PDFs, fill a form, and update a tracker in one tired session.</p>
<p>Here is a realistic week:</p>
<pre><code class="language-text">Monday: source roles and build a shortlist
Tuesday: create role configs
Wednesday: edit cover letters and custom answers
Thursday: generate PDFs and review them
Friday: apply, submit manually, and update tracker
</code></pre>
<p>You do not need to follow that exact schedule, but the separation is useful.</p>
<p>On sourcing days, I focus on finding roles that are worth the time.</p>
<p>I might run:</p>
<pre><code class="language-text">/source Germany backend
/source remote AI infrastructure
/source UK data platform
</code></pre>
<p>Then I read the shortlist and remove roles that do not fit. I am not trying to apply yet. I am trying to decide what deserves attention.</p>
<p>On role config days, I run <code>/new-role</code> for the strongest matches:</p>
<pre><code class="language-text">/new-role https://boards.greenhouse.io/example/jobs/123
/new-role https://jobs.lever.co/example/456
</code></pre>
<p>That gives me structured files under <code>roles/</code>.</p>
<p>At this point, the job application becomes editable data. I can inspect the title, location, ATS platform, CV variant, and custom answer fields. I can also keep the job description context close to the generated application materials.</p>
<p>On writing days, I edit the role configs.</p>
<p>This is where the application becomes specific.</p>
<p>I do not want a cover letter that says, "I am excited about your mission" and nothing else. I want the letter to mention the actual engineering problem, the product surface, the infrastructure shape, or the team context where I can add value.</p>
<p>For example, instead of this:</p>
<pre><code class="language-text">I am excited about the opportunity to contribute to your team.
</code></pre>
<p>I would rather write this:</p>
<pre><code class="language-text">Your backend platform work around payments, reliability, and developer-facing APIs maps closely to the systems work I have done across Python, Go, AWS, and Kubernetes.
</code></pre>
<p>That second version is still simple. It does not pretend to know private company details. But it is specific enough to show that the application was not generated blindly.</p>
<p>On generation days, I run:</p>
<pre><code class="language-text">/generate-cv &lt;role_id&gt;
</code></pre>
<p>Then I open the generated PDFs.</p>
<p>I do not review PDFs in bulk without reading them. That defeats the point. The generation step saves time, but the review step protects quality.</p>
<p>On submission days, I open the application forms and run <code>/apply</code>.</p>
<p>The goal is not to submit as fast as possible. The goal is to remove repeated typing so I can spend my energy reviewing the parts that matter.</p>
<p>That distinction is important.</p>
<p>If you are applying to ten roles in a day, the worst outcome is not that the work takes longer than expected. The worst outcome is sending ten weak or incorrect applications quickly.</p>
<h2>Keeping profile.json Honest</h2>
<p><code>profile.json</code> is the base layer of the whole workflow.</p>
<p>If it is inaccurate, every generated output can inherit that inaccuracy.</p>
<p>That means the profile deserves careful review before you rely on it.</p>
<p>After running <code>/setup-profile</code>, I check these sections first:</p>
<pre><code class="language-text">name
email
phone
location
links
headline
summary
skills
experience
education
variants
</code></pre>
<p>The easy fields are name, email, phone, and links. Still check them.</p>
<p>Phone numbers can lose country codes. LinkedIn URLs can be pasted without the full path. GitHub URLs can be wrong if your CV had an old link. These are small issues, but they are visible to recruiters.</p>
<p>The summary needs more attention.</p>
<p>A generated summary can sound correct but still not sound like you. It might over-index on one part of your background. It might sound too broad. It might use words you would not use in an interview. Edit it until you can read it out loud without feeling awkward.</p>
<p>Skills are another place where honesty matters.</p>
<p>I prefer listing skills I can defend in a technical conversation. If a tool extracts every technology that appears once in a project bullet, the skill list can become noisy. Remove tools you only touched lightly. Keep the ones that represent your real working experience.</p>
<p>Experience bullets deserve the most time.</p>
<p>A good bullet should include what you did, the system or product area, and the outcome where you can support it.</p>
<p>This is weak:</p>
<pre><code class="language-text">Worked on backend APIs.
</code></pre>
<p>This is better:</p>
<pre><code class="language-text">Built and maintained Python backend APIs for internal workflow automation, reducing repeated manual operations for support and operations teams.
</code></pre>
<p>If you have numbers, use them. If you do not have numbers, do not invent them.</p>
<p>It is better to write a clear factual bullet than to add a fake metric.</p>
<p>The variant sections are where career-agent becomes more useful.</p>
<p>Variant A can emphasize AI and evaluation work. Variant B can emphasize data platforms and pipelines. Variant C can emphasize backend systems and APIs. The facts should stay the same, but the ordering and framing can change.</p>
<p>That is normal.</p>
<p>Different roles care about different parts of the same background. The important part is that every variant remains true.</p>
<p>I also recommend keeping a clean backup of a reviewed profile:</p>
<pre><code class="language-bash">cp profile.json profile.reviewed.json
</code></pre>
<p>Note that both files can contain personal data, so keep them in the gitignored workspace. Do not commit them.</p>
<p>Before a serious application batch, I run a quick profile check:</p>
<pre><code class="language-bash">python3 -m json.tool profile.json &gt; /tmp/profile.checked.json
</code></pre>
<p>That verifies the JSON syntax. It does not verify the content, but it catches broken commas, missing quotes, or accidental edits that would break the workflow.</p>
<h2>How to Think About /source</h2>
<p><code>/source</code> is useful, but I do not treat it as a replacement for judgment.</p>
<p>I treat it as a shortlist builder.</p>
<p>That distinction keeps expectations realistic.</p>
<p>A sourcing command can search based on your profile, location, and keywords. It can return roles that look aligned. It can help organize signals like location, sponsorship wording, company, title, and fit score.</p>
<p>But job markets are messy.</p>
<p>Roles close without notice. Companies repost old roles. Some career pages keep expired roles indexed. Some job boards cache listings after the company page has changed. Some postings mention remote work in one section and office requirements in another.</p>
<p>So when <code>/source</code> returns a role, I still verify:</p>
<pre><code class="language-text">Is this role still live on the company site?
Is the application URL direct?
Is the location compatible with me?
Does the role match my level?
Does the role match the kind of work I want?
Is sponsorship or relocation actually stated?
</code></pre>
<p>I am careful with sponsorship language.</p>
<p>If a job post explicitly says sponsorship is available, that is a strong signal. If an uploaded company list says a company has sponsored before, that is not the same thing. It can help you decide where to look, but it does not prove this role sponsors now.</p>
<p>That is why the role config still needs review.</p>
<p>Use <code>/source</code> to reduce random browsing. Use your judgment to decide whether the role deserves a tailored application.</p>
<p>Here is the flow I recommend:</p>
<pre><code class="language-text">/source &lt;location&gt; &lt;role_type&gt;
read the shortlist
open the strongest company pages
verify the role is live
run /new-role on the real application URL
edit the role config
generate PDFs
apply with review
</code></pre>
<p>By using <code>/source</code> this way, you keep the benefit without pretending it knows everything.</p>
<h2>Reviewing Generated PDFs Like a Recruiter</h2>
<p>Generated PDFs should be reviewed visually and textually.</p>
<p>First, I read the CV as a person.</p>
<p>I ask:</p>
<pre><code class="language-text">Can I understand the role target in the first 10 seconds?
Does the summary match the job?
Are the strongest experiences near the top?
Do the skills match the role without looking stuffed?
Are the bullets specific?
Are dates and titles correct?
Are links present and readable?
</code></pre>
<p>Then I review it as an ATS would parse it.</p>
<p>That means I avoid being impressed by visual decoration. The layout is supposed to be plain. A single-column CV might look less fancy than a design-heavy resume, but it is easier to parse.</p>
<p>If you want to test extraction, you can use a local PDF text tool:</p>
<pre><code class="language-bash">pdftotext generated/&lt;file&gt;.pdf - | head -80
</code></pre>
<p>If <code>pdftotext</code> is not installed, skip this check or install Poppler through your package manager. This is optional, but useful when you want confidence that the PDF text is extractable.</p>
<p>For the cover letter, I check tone.</p>
<p>A cover letter should sound like a human wrote it for this role. It does not need to be dramatic. It needs to be specific, honest, and easy to read.</p>
<p>I look for these problems:</p>
<ul>
<li>It repeats the CV without adding context</li>
<li>It sounds too generic</li>
<li>It claims excitement without evidence</li>
<li>It mentions technology the company does not appear to use</li>
<li>It overstates my experience</li>
<li>It has placeholder text</li>
<li>It is too long for the role</li>
</ul>
<p>If I find those issues, I do not edit the PDF directly. I edit the role config or profile, then regenerate.</p>
<p>That keeps the source of truth clean.</p>
<h2>Using the Docs Page Alongside the Article</h2>
<p>This article is meant to teach the workflow from a practical angle.</p>
<p>The docs page is there for quick reference:</p>
<p><a href="https://nextwebb.github.io/career-agent/">nextwebb.github.io/career-agent</a> 📌</p>
<p>I would use both together.</p>
<p>Use the article when you want the full flow and the reasoning behind the boundaries. Use the docs page when you want the command list, repository context, and landing page reference.</p>
<p>That is why I wanted the GitHub Pages site live before publishing this.</p>
<p>If someone reads the article and wants to check the project quickly, they should have somewhere clear to go. The repo is the source of truth for code. The npm page is the source of truth for the package. The docs page gives the project a cleaner entry point.</p>
<p>The three links serve different jobs:</p>
<pre><code class="language-text">GitHub repo: code, issues, PRs, license
npm package: installable package metadata
docs page: quick project overview
</code></pre>
<p>Here they are:</p>
<p><a href="https://github.com/nextwebb/career-agent">github.com/nextwebb/career-agent</a></p>
<p><a href="https://www.npmjs.com/package/@nextwebb/career-agent">@nextwebb/career-agent</a> 📦</p>
<p><a href="https://nextwebb.github.io/career-agent/">nextwebb.github.io/career-agent</a> 📌</p>
<h2>How Contributors Can Help</h2>
<p>The most useful contributions are not cosmetic.</p>
<p>The best contributions improve one of three things:</p>
<pre><code class="language-text">ATS coverage
field mapping reliability
documentation from real usage
</code></pre>
<p>ATS coverage matters because companies use different systems.</p>
<p>Greenhouse, Lever, and Workable are handled today. But many companies use Ashby, iCIMS, SmartRecruiters, Taleo, BambooHR, custom Workday flows, or embedded forms. Each platform has its own field patterns, file upload behavior, and validation rules.</p>
<p>If you want to contribute a new ATS adapter, start by documenting the page shape.</p>
<p>Useful notes include:</p>
<pre><code class="language-text">ATS platform name
example public application URL
field labels shown on page
input names or IDs if visible
file upload behavior
required fields
custom question behavior
submit button behavior
fields the tool should never fill
</code></pre>
<p>Do not include private applicant data in an issue.</p>
<p>If the page can be shared publicly, include the job URL. If it cannot, describe the structure and redact sensitive information.</p>
<p>Field mapping reliability is the second big area.</p>
<p>This is where small improvements matter. A field labeled "Preferred name" should not always map to legal first name. A field labeled "Current company" should not map to target company. A field labeled "Website" might accept GitHub, personal site, or portfolio depending on the form.</p>
<p>These are not glamorous problems, but they are the real problems in application automation.</p>
<p>Documentation is the third area.</p>
<p>If you use the tool and find a confusing step, open an issue. If a command output is unclear, say so. If a setup step fails on Linux, Windows, or a different Python environment, include the exact output.</p>
<p>Good bug reports make the tool better faster.</p>
<p>Useful issue format:</p>
<pre><code class="language-text">Command:
Expected:
Actual:
OS:
Node version:
Python version:
Claude Code version:
ATS platform, if relevant:
Redacted screenshot or terminal output:
</code></pre>
<p>That gives enough context for someone to reproduce the issue without guessing.</p>
<h2>Known Boundaries</h2>
<p>career-agent handles repeated work. It does not remove your responsibility.</p>
<p>It does not submit applications.</p>
<p>It does not answer EEO or voluntary self-identification fields.</p>
<p>It does not enter passwords or credentials.</p>
<p>It does not guarantee every ATS page will work.</p>
<p>It does not make weak applications strong.</p>
<p>It does not replace your judgment about whether a role is worth applying to.</p>
<p>That last point matters. A tool can help you apply more consistently, but it cannot decide what kind of work you should do next. It cannot know which company culture you want. It cannot know whether a role is worth your time. It can help with the structure, not the life decision.</p>
<p>There is also a documentation boundary around <code>/source</code>.</p>
<p>The command is designed to find and verify open roles, score fit, and return application URLs. But any sourcing workflow should still be checked against the company career page. Roles close. Pages change. Sponsorship language can be unclear.</p>
<p>Use <code>/source</code> to build a shortlist. Do not treat it as a final truth source.</p>
<h2>Troubleshooting Notes</h2>
<p>If <code>npx @nextwebb/career-agent</code> fails, check Node first:</p>
<pre><code class="language-bash">node --version
</code></pre>
<p>You need Node 18 or newer.</p>
<p>If setup says Python is missing, check:</p>
<pre><code class="language-bash">python3 --version
python3.11 --version
</code></pre>
<p>The installer looks for Python 3.10 or newer. On some machines, <code>python3</code> may point to an older version while <code>python3.11</code> exists.</p>
<p>If <code>reportlab</code> is missing, install it:</p>
<pre><code class="language-bash">python3 -m pip install reportlab
</code></pre>
<p>If <code>claude --version</code> fails, install or fix Claude Code CLI before continuing.</p>
<p>If the slash commands do not appear after install, restart Claude Code and check the plugin settings.</p>
<p>If <code>/apply</code> cannot control Chrome, check that Claude in Chrome is installed and connected to Claude Code. Again, signing in to Claude.ai alone is not the same thing.</p>
<h2>Contributing</h2>
<p>The code is open source and MIT licensed.</p>
<p>The repo is here:</p>
<p><a href="https://github.com/nextwebb/career-agent">github.com/nextwebb/career-agent</a></p>
<p>The live docs are here:</p>
<p><a href="https://nextwebb.github.io/career-agent/">nextwebb.github.io/career-agent</a> 📌</p>
<p>The npm package is here:</p>
<p><a href="https://www.npmjs.com/package/@nextwebb/career-agent">@nextwebb/career-agent</a> 📦</p>
<p>The main contribution area is ATS support.</p>
<p>Greenhouse, Lever, and Workable are the current supported platforms. Ashby, iCIMS, SmartRecruiters, Taleo, and BambooHR are good candidates for future support.</p>
<p>If you want to contribute, open an issue first. Include the ATS platform, a sample public application URL if you can share one, what fields were missed, and what behavior you expected.</p>
<p>Good issues make automation work easier.</p>
<h2>Wrapping Up</h2>
<p>career-agent gives you a local Claude Code workflow for preparing job applications.</p>
<p>It helps you build a profile, source roles, create role configs, generate tailored PDFs, fill supported ATS forms, and track your pipeline.</p>
<p>It does not remove the need to think.</p>
<p>That is the point.</p>
<p>Use it for the repeated work. Slow down where accuracy, consent, and judgment matter.</p>
<p>Star the repo: <a href="https://github.com/nextwebb/career-agent">github.com/nextwebb/career-agent</a> ⭐</p>
<p>Read the docs: <a href="https://nextwebb.github.io/career-agent/">nextwebb.github.io/career-agent</a> 📌</p>
<p>npm package: <a href="https://www.npmjs.com/package/@nextwebb/career-agent">@nextwebb/career-agent</a> 📦</p>
<p>Upvote on Product Hunt if this was useful: <a href="https://www.producthunt.com/posts/career-agent">career-agent</a> 🚀</p>
<p>If you find a bug or want to add ATS support for Ashby, iCIMS, SmartRecruiters, Taleo, or another platform, open an issue or send a PR.</p>
<p>If you have any inquiries or wish to gain additional knowledge, please get in touch with me on <a href="https://github.com/nextwebb">GitHub</a>, <a href="https://twitter.com/Iam_nextwebb">Twitter/X</a>, or <a href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>. Kindly show your support by leaving a thumbs up 👍, a comment 💬, and sharing this article with your network 😊.</p>
]]></content:encoded></item><item><title><![CDATA[Data Engineering Foundations: A Hands-On Guide]]></title><description><![CDATA[Data Engineering Foundations: A Hands-On Guide
Hey there! If you’ve been curious about data engineering, this guide will help you understand the basics and walk you through practical examples. Whether it’s setting up storage, processing data, automat...]]></description><link>https://blog.nextwebb.tech/data-engineering-foundations-a-hands-on-guide</link><guid isPermaLink="true">https://blog.nextwebb.tech/data-engineering-foundations-a-hands-on-guide</guid><category><![CDATA[ETLPipelines]]></category><category><![CDATA[BigDataTools]]></category><category><![CDATA[LearnDataEngineering]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[apache-airflow]]></category><category><![CDATA[Python]]></category><category><![CDATA[spark]]></category><category><![CDATA[#ApacheAirflow]]></category><category><![CDATA[#apache-spark]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[unix]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[backend]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Fri, 10 Jan 2025 01:06:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fyeOxvYvIyY/upload/1d00e704d7549e477f6e30ebb1accc5d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Data Engineering Foundations: A Hands-On Guide</strong></p>
<p>Hey there! If you’ve been curious about data engineering, this guide will help you understand the basics and walk you through practical examples. Whether it’s setting up storage, processing data, automating workflows, or monitoring systems, I’ll keep it simple, relatable, and fun! 😊</p>
<h3 id="heading-what-is-data-engineering"><strong>What is Data Engineering?</strong></h3>
<p>Data engineering involves organizing, processing, and automating data workflows to make raw data useful for analysis and decision-making. Here’s what we’ll cover:</p>
<ol>
<li><strong>Storage</strong>: Where and how data lives.</li>
<li><strong>Processing</strong>: Cleaning and transforming raw data.</li>
<li><strong>Automation</strong>: Running workflows seamlessly.</li>
<li><strong>Monitoring</strong>: Ensuring everything runs smoothly.</li>
</ol>
<p>Let’s dive into each step! 🚀</p>
<hr />
<h3 id="heading-setting-up-your-environment-and-prerequisites"><strong>Setting Up Your Environment and Prerequisites</strong></h3>
<p>Before we start, let’s set the stage for what you’ll need:</p>
<ol>
<li><p><strong>Environment</strong>:</p>
<ul>
<li>A Unix-based system (MacOS) or Windows Subsystem for Linux (WSL).</li>
<li>Python installed (preferably version 3.11).</li>
<li>PostgreSQL installed and running locally.</li>
</ul>
</li>
<li><p><strong>Prerequisites</strong>:</p>
<ul>
<li><strong>Basic Command Line Knowledge</strong>: We’ll use terminal commands for installations and setups.</li>
<li><strong>Python Basics</strong>: Familiarity with Python syntax.</li>
<li><strong>Administrative Access</strong>: To install and configure software.</li>
</ul>
</li>
<li><p><strong>Architectural Overview</strong>:
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736468670/Screenshot_2025-01-10_at_01.21.45.png" alt="Flow of the ETL process" /></p>
</li>
</ol>
<p>The diagram above provides a high-level view of how the component's workflow interacts;
This modular approach allows each tool to do what it’s best: Airflow for orchestration, Spark for distributed data processing, and PostgreSQL for structured data storage.</p>
<ol start="4">
<li><p><strong>Install Necessary Tools</strong>:</p>
<ul>
<li>PostgreSQL:<pre><code class="lang-bash">brew update
brew install postgresql
</code></pre>
</li>
<li>PySpark:<pre><code class="lang-bash">brew install apache-spark
</code></pre>
</li>
<li><p>Airflow:</p>
<pre><code class="lang-bash">python -m venv airflow_env
<span class="hljs-built_in">source</span> airflow_env/bin/activate  <span class="hljs-comment"># macOS/Linux</span>
<span class="hljs-comment">#https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt</span>
pip install <span class="hljs-string">"apache-airflow[postgres]=="</span> --constraint 
<span class="hljs-string">"https://raw.githubusercontent.com/apache/airflow/constraints-2.10.4/constraints-3.11.txt"</span>

airflow db migrate <span class="hljs-comment">#Initialize the Database</span>
</code></pre>
<p>      <img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736343520/Screenshot_2025-01-08_at_14.29.14.png" alt="image" /></p>
</li>
</ul>
</li>
</ol>
<p>Now that we’re ready, let’s dive into each component! 💻</p>
<hr />
<h3 id="heading-1-storage-databases-and-file-systems"><strong>1. Storage: Databases and File Systems</strong></h3>
<p>Data storage forms the foundation of data engineering. Storage can be broadly categorized into:</p>
<ul>
<li><p><strong>Databases</strong>:</p>
<ul>
<li>Efficiently organized.</li>
<li>Features include search, replication, and indexing.</li>
<li>Examples:<ul>
<li><strong>SQL Databases</strong>: Structured data (e.g., PostgreSQL, MySQL).</li>
<li><strong>NoSQL Databases</strong>: Schema-less (e.g., MongoDB, Redis).</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>File Systems</strong>:</p>
<ul>
<li>Suitable for unstructured data.</li>
<li>Limited features compared to databases.</li>
</ul>
</li>
</ul>
<h4 id="heading-setting-up-postgresql"><strong> Setting Up PostgreSQL</strong></h4>
<ol>
<li><p><strong>Start the Service</strong>:</p>
<pre><code class="lang-bash">brew services start postgresql
</code></pre>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736344175/Screenshot_2025-01-08_at_14.48.57.png" alt="Start the Postgresql Service" /></p>
</li>
<li><p><strong>Create a Database, Connect to Database and Create Table</strong>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> sales_data;
\c sales_data
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> sales (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    item_name <span class="hljs-built_in">TEXT</span>,
    amount <span class="hljs-built_in">NUMERIC</span>,
    sale_date <span class="hljs-built_in">DATE</span>
);
</code></pre>
</li>
<li><p><strong>Insert Sample Data</strong>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> sales (item_name, amount, sale_date)
<span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'Laptop'</span>, <span class="hljs-number">1200</span>, <span class="hljs-string">'2024-01-10'</span>),
       (<span class="hljs-string">'Phone'</span>, <span class="hljs-number">800</span>, <span class="hljs-string">'2024-01-12'</span>);
</code></pre>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736345021/Screenshot_2025-01-08_at_15.03.00.png" alt="Create a Database &amp; Insert Sample Data" /></p>
</li>
</ol>
<p>Now your data is safely stored in PostgreSQL! 🏠</p>
<hr />
<h3 id="heading-2-processing-pyspark-and-distributed-computing"><strong>2. Processing: PySpark and Distributed Computing</strong></h3>
<p>Processing frameworks enable you to transform raw data into actionable insights. Apache Spark, with its distributed computing model, is one such framework that’s widely used.</p>
<ul>
<li><strong>Batch and Stream Processing</strong>:<ul>
<li>Batch: Processes data in chunks.</li>
<li>Stream: Processes data in real-time.</li>
</ul>
</li>
<li><strong>Tools</strong>: Apache Spark, Flink, Kafka, Hive.</li>
</ul>
<h4 id="heading-processing-data-with-pyspark"><strong> Processing Data with PySpark</strong></h4>
<ol>
<li><p><strong>Install Java and  PySpark</strong>:</p>
<pre><code class="lang-bash">brew install openjdk@11 &amp;&amp; brew install apache-spark
</code></pre>
</li>
<li><p><strong>Load Data from a CSV</strong>:
Create a <code>sales.csv</code> file:</p>
<pre><code class="lang-csv">id,item_name,amount,sale_date
1,Laptop,1200,2024-01-10
2,Phone,800,2024-01-12
</code></pre>
<p>Python script to load and process:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pyspark.sql <span class="hljs-keyword">import</span> SparkSession

spark = SparkSession.builder.appName(<span class="hljs-string">"DataProcessing"</span>).getOrCreate()

<span class="hljs-comment"># Load CSV File</span>
df = spark.read.csv(<span class="hljs-string">"sales.csv"</span>, header=<span class="hljs-literal">True</span>, inferSchema=<span class="hljs-literal">True</span>)
df.show()
</code></pre>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736349760/Screenshot_2025-01-08_at_16.21.38.png" alt="Python script to load and process" />
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736349760/Screenshot_2025-01-08_at_16.21.19.png" alt="Spark UI" /></p>
</li>
<li><p><strong>Filter High-Value Sales</strong>:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Filter sales above $1000</span>
high_value_sales = df.filter(df[<span class="hljs-string">"amount"</span>] &gt; <span class="hljs-number">1000</span>)
high_value_sales.show()
</code></pre>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736351366/Screenshot_2025-01-08_at_16.28.50.png" alt="Filter High-Value Sales" /></p>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736351366/Screenshot_2025-01-08_at_16.28.50.png" alt="Spark UI - High-Value Sales" /></p>
</li>
<li><p><strong>Setup Postgres DB driver</strong>:
If you don’t find the driver, download it directly from the PostgreSQL website:
<code>curl -O https://jdbc.postgresql.org/download/postgresql-42.6.0.jar</code>
This will download the driver to your current working directory.</p>
</li>
</ol>
<p>Before running the script, verify the .jar file path:</p>
<pre><code>ls -l /path/to/postgresql<span class="hljs-number">-42.6</span><span class="hljs-number">.0</span>.jar
</code></pre><p>Once you locate the driver file, use its full path in your script.</p>
<pre><code>     #Add the PostgreSQL JDBC driver path
     spark = SparkSession.builder \
    .appName(<span class="hljs-string">"DataProcessing"</span>) \
    .config(<span class="hljs-string">"spark.jars"</span>, <span class="hljs-string">"/path/to/postgresql-42.6.0.jar"</span>) \
    .getOrCreate()
</code></pre><p>5).  <strong>Save Processed Data Back to PostgreSQL</strong>:</p>
<pre><code class="lang-python">     <span class="hljs-comment">#Add the PostgreSQL JDBC driver path</span>
     spark = SparkSession.builder \
    .appName(<span class="hljs-string">"DataProcessing"</span>) \
    .config(<span class="hljs-string">"spark.jars"</span>, <span class="hljs-string">"/path/to/postgresql-42.6.0.jar"</span>) \
    .getOrCreate()

   high_value_sales.write.format(<span class="hljs-string">"jdbc"</span>).options(
       url=<span class="hljs-string">"jdbc:postgresql://localhost:5432/sales_data"</span>,
       driver=<span class="hljs-string">"org.postgresql.Driver"</span>,
       dbtable=<span class="hljs-string">"high_value_sales"</span>,
       user=<span class="hljs-string">"your_user"</span>,
       password=<span class="hljs-string">"your_password"</span>
   ).mode(<span class="hljs-string">"append"</span>).save()
</code></pre>
<p>   <img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736351246/Screenshot_2025-01-08_at_16.40.51.png" alt="Save Processed Data Back to PostgreSQL" /></p>
<p>  <img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736352202/Screenshot_2025-01-08_at_17.03.06.png" alt="high_value_sales table" /></p>
<p>  <img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736351247/Screenshot_2025-01-08_at_16.40.33.png" alt="Spark UI - Save Processed Data Back to PostgreSQL" />
Great work! You’ve processed data using Spark. 🚀</p>
<hr />
<h3 id="heading-3-automation-airflow"><strong>3. Automation: Airflow</strong></h3>
<p>Automation helps in managing workflows by setting up dependencies and schedules. Tools like Airflow, Oozie, and Luigi simplify this process.</p>
<h4 id="heading-automating-etl-with-airflow"><strong> Automating ETL with Airflow</strong></h4>
<ol>
<li><p><strong>Initialize apache Airflow</strong>:</p>
<pre><code class="lang-bash">airflow db migrate

airflow users create \
    --username admin \
    --firstname Admin \
    --lastname User \
    --role Admin \
    --email admin@example.com
airflow webserver &amp;
</code></pre>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736360442/Screenshot_2025-01-08_at_19.18.48.png" alt="Initialize apache Airflow" /></p>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736360442/Screenshot_2025-01-08_at_19.18.48.png" alt="Create airflow admin user" /></p>
</li>
<li><p><strong>Create a Workflow (DAG)</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> airflow <span class="hljs-keyword">import</span> DAG
<span class="hljs-keyword">from</span> airflow.operators.bash <span class="hljs-keyword">import</span> BashOperator
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime

default_args = {
  <span class="hljs-string">'owner'</span>: <span class="hljs-string">'airflow'</span>,
  <span class="hljs-string">'start_date'</span>: datetime(<span class="hljs-number">2024</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>),
  <span class="hljs-string">'email'</span>: [<span class="hljs-string">'alerts@example.com'</span>],
  <span class="hljs-string">'email_on_failure'</span>: <span class="hljs-literal">True</span>,
}

<span class="hljs-keyword">with</span> DAG(<span class="hljs-string">'spark_etl_pipeline'</span>, default_args=default_args, schedule_interval=<span class="hljs-string">'@daily'</span>) <span class="hljs-keyword">as</span> dag:
 <span class="hljs-comment">#Update with the absolute path to your script</span>
 spark_etl_task = BashOperator(
     task_id=<span class="hljs-string">'spark_etl'</span>,
     bash_command=<span class="hljs-string">'python3 /absolute/path/to/sales.py'</span>,  <span class="hljs-comment"># Replace with the actual path to sales.py</span>
 )

 <span class="hljs-comment">#Example additional tasks for data verification</span>
 verify_task = BashOperator(
     task_id=<span class="hljs-string">'verify_results'</span>,
     bash_command=<span class="hljs-string">'echo "Verification complete!"'</span>
 )

 <span class="hljs-comment">#Define the workflow</span>
 spark_etl_task &gt;&gt; verify_task
</code></pre>
<p>In this DAG:
  The schedule_interval='@hourly' ensures the ETL pipeline runs every hour.
  The bash_command executes the PySpark ETL script (sales.py).
  Email alerts notify the owner in case of failures.</p>
</li>
<li><p><strong>Monitor Your Workflow</strong>:</p>
<p>Move the DAG file to Airflow’s dags/ directory:</p>
</li>
</ol>
<pre><code>mv spark_etl_pipeline.py ~<span class="hljs-regexp">/airflow/</span>dags/
</code></pre><p> Restart Airflow services:</p>
<pre><code>airflow scheduler
airflow webserver
</code></pre><p>Check the spark_etl_pipeline DAG, and ensure it is enabled. Trigger it manually to validate.</p>
<p>Open the Airflow UI at <code>http://localhost:8080</code>. Check task statuses and logs.
<img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736407165/Screenshot_2025-01-09_at_08.18.41.png" alt="Apache Airflow dashboard UI" /></p>
<hr />
<h3 id="heading-4-monitoring"><strong>4. Monitoring</strong></h3>
<p>Monitoring ensures pipelines run reliably. You can use tools like Airflow’s alerting system or integrate with Grafana and Prometheus.</p>
<h4 id="heading-from-the-airflow-ui"><strong>From the Airflow UI</strong></h4>
<ul>
<li><strong>Task Status</strong>: Check the <strong>Graph View</strong> at <a target="_blank" href="http://localhost:8080">http://localhost:8080</a> to monitor task states:<ul>
<li><strong>Green</strong>: Success</li>
<li><strong>Red</strong>: Failure</li>
<li><strong>Yellow</strong>: Running</li>
<li><strong>Grey</strong>: Queued/Skipped</li>
</ul>
</li>
<li><strong>Logs</strong>: Click on a task instance to view logs for debugging. </li>
</ul>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1736408544/Screenshot_2025-01-09_at_08.42.07.png" alt="Airflow UI" /></p>
<hr />
<h3 id="heading-wrapping-up"><strong>Wrapping Up</strong></h3>
<p>Congrats! 🎉 You’ve learned how to:</p>
<ul>
<li>Set up storage with PostgreSQL.</li>
<li>Process data using PySpark.</li>
<li>Automate workflows with Airflow.</li>
<li>Monitor systems for reliability.</li>
</ul>
<p><strong>Data Engineering as a Career Focus</strong></p>
<p>Data engineering is the backbone of modern data workflows. It focuses on building systems that collect, store, and process data efficiently, enabling teams like analysts, data scientists, and business stakeholders to extract value from it.</p>
<p>While it primarily involves skills like coding, system design, and infrastructure management, data engineering often intersects with adjacent fields: backend development (e.g., APIs, database schema design), DevOps (e.g., CI/CD pipelines, infrastructure as code), and data operations (e.g., managing ETL workflows and ensuring data reliability).</p>
<p>Whether you're orchestrating workflows with tools like Apache Airflow, processing massive datasets with Apache Spark, or managing databases like PostgreSQL, your role is pivotal in enabling data-driven decisions.</p>
<p>Start small, explore, and grow your expertise! 💪</p>
<p>If you have any inquiries or wish to gain additional knowledge, please get in touch with me on <a target="_blank" href="github.com/nextwebb">GitHub</a>, <a target="_blank" href="https://x.com/iam_nextwebb">Twitter</a>, or <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>. Kindly show your support by leaving a thumbs up 👍, a comment 💬, and sharing this article with your network 😊.</p>
<p><strong>References to Explore Further:</strong></p>
<ul>
<li><a target="_blank" href="https://spark.apache.org/docs">Apache Spark Documentation</a></li>
<li><a target="_blank" href="https://airflow.apache.org">Airflow Official Site</a></li>
<li><a target="_blank" href="https://www.postgresql.org/docs">PostgreSQL Documentation</a></li>
<li><a target="_blank" href="https://raw.githubusercontent.com/apache/airflow/constraints-2.10.4/constraints-3.11.txt">Constraints file for each version of Python that Airflow supports</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Avoiding Pitfalls in Amazon S3: Handling Case Sensitivity in Python Workflows]]></title><description><![CDATA[When working with Amazon S3, it’s easy to overlook an important nuance: case sensitivity. While bucket names are case-insensitive, object keys (file paths) are case-sensitive. This distinction can lead to unexpected bugs in your workflows. For instan...]]></description><link>https://blog.nextwebb.tech/avoiding-pitfalls-in-amazon-s3-handling-case-sensitivity-in-python-workflows</link><guid isPermaLink="true">https://blog.nextwebb.tech/avoiding-pitfalls-in-amazon-s3-handling-case-sensitivity-in-python-workflows</guid><category><![CDATA[AWS s3]]></category><category><![CDATA[Python]]></category><category><![CDATA[#CloudStorage #DataManagement #CloudServices #DigitalStorage #FileSharing #CloudSecurity #OnlineBackup #DataPrivacy #HybridCloud #EnterpriseStorage]]></category><category><![CDATA[data pipeline]]></category><category><![CDATA[error handling]]></category><category><![CDATA[AwsTips]]></category><category><![CDATA[Cloud Development ]]></category><category><![CDATA[DataIntegration]]></category><category><![CDATA[#DataIntegration #DataManagement #IntegrationSolutions]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Fri, 29 Nov 2024 12:53:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/klWUhr-wPJ8/upload/3c60af4ca61b972c0d05ddf4cd12e77e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working with <strong>Amazon S3</strong>, it’s easy to overlook an important nuance: <strong>case sensitivity</strong>. While bucket names are <strong>case-insensitive</strong>, object keys (file paths) are <strong>case-sensitive</strong>. This distinction can lead to unexpected bugs in your workflows. For instance, <code>my-bucket/data/file.txt</code> and <code>my-bucket/Data/File.txt</code> are treated as completely different objects.</p>
<p>If you’ve ever had a Python script fail to locate files in S3, chances are, case sensitivity might have been the issue.</p>
<hr />
<h3 id="heading-why-does-case-sensitivity-matter"><strong>Why Does Case Sensitivity Matter?</strong></h3>
<p>Let’s say your data processing pipeline dynamically generates S3 paths based on inputs from multiple teams. One team might upload to <code>my-bucket/data/</code>, while another uses <code>my-bucket/Data/</code>. Without a strategy to handle case mismatches, your pipeline could skip files or fail altogether, causing inefficiencies and delays.</p>
<hr />
<h3 id="heading-how-to-handle-case-sensitivity-in-python"><strong>How to Handle Case Sensitivity in Python</strong></h3>
<p>Here’s how you can address this:</p>
<ol>
<li><p><strong>Normalize Paths</strong>:</p>
<ul>
<li>Standardize paths to lowercase (or a consistent format) during both upload and access.</li>
</ul>
</li>
<li><p><strong>Verify Object Keys</strong>:</p>
<ul>
<li>Use AWS SDK methods like <code>list_objects_v2</code> to confirm the existence of object keys.</li>
</ul>
</li>
<li><p><strong>Implement Error Handling</strong>:</p>
<ul>
<li>Design scripts to handle exceptions <code>KeyError</code> and log issues for debugging.</li>
</ul>
</li>
</ol>
<hr />
<h3 id="heading-code-example-listing-objects-safely"><strong>Code Example: Listing Objects Safely</strong></h3>
<p>Below is a Python script to list objects in an S3 bucket while addressing case sensitivity:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> boto3

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">normalize_s3_path</span>(<span class="hljs-params">bucket, prefix</span>):</span>
    <span class="hljs-string">"""
    Normalize and validate S3 paths to handle case sensitivity.

    Args:
        bucket (str): Name of the S3 bucket.
        prefix (str): Prefix (folder path) in the bucket.

    Returns:
        list: Canonical paths matching the prefix.
    """</span>
    s3 = boto3.client(<span class="hljs-string">'s3'</span>)
    response = s3.list_objects_v2(Bucket=bucket, Prefix=prefix.lower())

    <span class="hljs-keyword">if</span> <span class="hljs-string">'Contents'</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> response:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Path '<span class="hljs-subst">{prefix}</span>' not found. Check case sensitivity."</span>)

    <span class="hljs-keyword">return</span> [obj[<span class="hljs-string">'Key'</span>] <span class="hljs-keyword">for</span> obj <span class="hljs-keyword">in</span> response[<span class="hljs-string">'Contents'</span>]]

<span class="hljs-comment"># Example usage</span>
bucket_name = <span class="hljs-string">"my-bucket"</span>
s3_path = <span class="hljs-string">"Data/File.txt"</span>

<span class="hljs-keyword">try</span>:
    files = normalize_s3_path(bucket_name, s3_path)
    print(<span class="hljs-string">"Canonical paths found:"</span>, files)
<span class="hljs-keyword">except</span> ValueError <span class="hljs-keyword">as</span> e:
    print(<span class="hljs-string">"Error:"</span>, e)
</code></pre>
<p>This script ensures that your workflow identifies object keys, regardless of mismatched cases in input paths.</p>
<hr />
<h3 id="heading-real-world-scenario"><strong>Real-World Scenario</strong></h3>
<p>In many data processing workflows, case mismatches in file paths can lead to missing or duplicated records. For instance, a team processing customer records stored in S3 noticed recurring errors due to inconsistent casing in object keys. By implementing strategies like normalizing paths and validating keys, they were able to significantly reduce these issues and improve the reliability of their data pipelines.</p>
<h3 id="heading-key-takeaways"><strong>Key Takeaways</strong></h3>
<ul>
<li><p><strong>Standardize</strong>: Use consistent casing for all S3 paths.</p>
</li>
<li><p><strong>Validate</strong>: Leverage AWS SDKs to confirm object key existence.</p>
</li>
<li><p><strong>Handle Errors Gracefully</strong>: Design scripts to log and report mismatched paths.</p>
</li>
</ul>
<p>By addressing case sensitivity early in your workflow, you can prevent costly errors and build more resilient systems.</p>
<h3 id="heading-what-about-you"><strong>What About You?</strong></h3>
<p>Have you faced challenges with case sensitivity in S3? Share your experiences in the comments or connect with me to discuss more strategies for optimizing cloud workflows!</p>
<p>If you have any inquiries or wish to gain additional knowledge, please get in touch with me on <a target="_blank" href="https://github.com/"><strong>GitHub</strong></a>, <a target="_blank" href="https://twitter.com/"><strong>Twitter</strong></a>, or <a target="_blank" href="https://linkedin.com/"><strong>LinkedIn</strong></a>. Kindly show your support by leaving a thumbs up 👍, a comment 💬, and sharing this article with your network 😊.</p>
<hr />
<h3 id="heading-references"><strong>References</strong></h3>
<ul>
<li><strong>Naming Amazon S3 Objects</strong>: <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html">AWS Documentation</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[CloudWatch Events vs. EventBridge: Which is Best for Your AWS Glue Jobs and Step Functions?]]></title><description><![CDATA[🌩️ Cloud computing has revolutionized how applications are developed and deployed, providing a more flexible and scalable environment for running workloads. AWS offers several services for event-driven computing, including Amazon CloudWatch Events a...]]></description><link>https://blog.nextwebb.tech/cloudwatch-events-vs-eventbridge-which-is-best-for-your-aws-glue-jobs-and-step-functions</link><guid isPermaLink="true">https://blog.nextwebb.tech/cloudwatch-events-vs-eventbridge-which-is-best-for-your-aws-glue-jobs-and-step-functions</guid><category><![CDATA[#CloudWatch]]></category><category><![CDATA[AWS]]></category><category><![CDATA[AWS Glue]]></category><category><![CDATA[AWS EventBridge]]></category><category><![CDATA[event-driven-architecture]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Tue, 28 Mar 2023 18:43:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Lno6-CxVXgo/upload/dc35c48d33ecdc4cbe86c4e1b1baa73f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>🌩️ Cloud computing has revolutionized how applications are developed and deployed, providing a more flexible and scalable environment for running workloads. AWS offers several services for event-driven computing, including Amazon CloudWatch Events and Amazon EventBridge. This article will explore the differences between CloudWatch Events and EventBridge and discuss which is best for your AWS Glue Jobs and Step Functions.</p>
<p>🎯 This article targets developers, architects, and AWS users who want to better understand the differences between CloudWatch Events and EventBridge and choose the most suitable service for triggering AWS Glue Jobs and Step Functions workflows.</p>
<h3 id="heading-understanding-event-driven-computing-on-aws">🌟 Understanding Event-Driven Computing on AWS</h3>
<p>According to the AWS documentation, event-driven computing is a computing model in which an application responds to events, which can be generated either by a user, a software system, or a hardware device. AWS offers several services for event-driven computing, including Amazon CloudWatch Events and Amazon EventBridge.</p>
<h3 id="heading-cloudwatch-events-vs-eventbridge-key-differences">💻 CloudWatch Events vs. EventBridge: Key Differences</h3>
<p>When choosing between CloudWatch Events and EventBridge, it is essential to understand their differences.</p>
<p>👴 <strong>Maturity</strong>: CloudWatch Events is an older service compared to EventBridge. Suppose you already use CloudWatch Events in your environment and only need basic event routing and triggering for Glue Jobs and Step Functions. In that case, it might make sense to continue using CloudWatch Events.</p>
<p>🔌 <strong>Flexibility and Integration</strong>: EventBridge offers more flexibility and integration capabilities, especially when working with custom events or third-party SaaS providers. If your Glue Jobs or Step Functions workflows require complex event filtering, transformation, or integration with external services, EventBridge is a better choice.</p>
<p>🚌 <strong>Event Bus and Event Routing</strong>: EventBridge uses an event bus to manage and route events, providing better control and visibility over event routing and delivery. This feature can be beneficial when coordinating multiple event sources and targets or needing more granular control over event processing.</p>
<p><img src="https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2022/04/06/pic1.png" alt="Building an event-driven application with Amazon EventBridge | AWS Compute  Blog" /></p>
<p>🚀 <strong>Scalability</strong>: EventBridge is designed to handle large volumes of events and offers better scaling capabilities than CloudWatch Events. If you anticipate high event throughput for your Glue Jobs or Step Functions workflows, consider using EventBridge.</p>
<h3 id="heading-using-cloudwatch-events-with-aws-glue-jobs">🌩️ Using CloudWatch Events with AWS Glue Jobs</h3>
<p>AWS Glue is a fully-managed extract, transform, and load (ETL) service that enables you to move data between data stores. In addition, you can use CloudWatch Events to trigger Glue jobs when specific events occur in your AWS environment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1680006385602/06c8fda0-0a63-4264-96be-50b020b25cc3.png" alt class="image--center mx-auto" /></p>
<p>👉 Here's an example of how you can use a CloudWatch Events rule to trigger a Glue job when a new object is created in an S3 bucket:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"source"</span>:[
      <span class="hljs-string">"aws.s3"</span>
   ],
   <span class="hljs-attr">"detail-type"</span>:[
      <span class="hljs-string">"AWS API Call via CloudTrail"</span>
   ],
   <span class="hljs-attr">"detail"</span>:{
      <span class="hljs-attr">"eventName"</span>:[
         <span class="hljs-string">"PutObject"</span>
      ],
      <span class="hljs-attr">"requestParameters"</span>:{
         <span class="hljs-attr">"bucketName"</span>:[
            <span class="hljs-string">"my-bucket"</span>
         ]
      }
   }
}
</code></pre>
<h3 id="heading-using-eventbridge-with-aws-step-functions">🌩️ Using EventBridge with AWS Step Functions</h3>
<p>AWS Step Functions is a fully-managed service that enables you to coordinate multiple AWS services into serverless workflows. You can use EventBridge to trigger Step Functions workflows when specific events occur in your AWS environment.</p>
<p><a target="_blank" href="https://aws.amazon.com/blogs/big-data/build-a-serverless-event-driven-workflow-with-aws-glue-and-amazon-eventbridge/"><img src="https://d2908q01vomqb2.cloudfront.net/b6692ea5df920cad691c20319a6fffd7a4a766b8/2021/06/30/BDB1575-image001.jpg" alt="Build a serverless event-driven workflow with AWS Glue and Amazon  EventBridge | AWS Big Data Blog" /></a></p>
<p>👉 Here's an example of how you can use an EventBridge rule to trigger a Step Functions workflow when an EC2 instance is stopped:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"source"</span>:<span class="hljs-string">"aws.ec2"</span>,
   <span class="hljs-attr">"detail-type"</span>:<span class="hljs-string">"EC2 Instance State-change Notification"</span>,
   <span class="hljs-attr">"detail"</span>:{
      <span class="hljs-attr">"state"</span>:<span class="hljs-string">"stopped"</span>
   }
}
</code></pre>
<h3 id="heading-prerequisites-and-best-practices">🔍 Prerequisites and Best Practices</h3>
<p>Before using CloudWatch Events or EventBridge with Glue jobs or Step Functions, consider the following prerequisites and best practices:</p>
<p>✅ Ensure that your AWS environment is configured correctly and you have the necessary permissions to create and manage CloudWatch Events or EventBridge rules and targets.</p>
<p>🎯 Define explicit event schemas and rules that match your specific use cases and requirements.</p>
<p>🔎 Use filtering and transformation capabilities to limit the number of events processed and to optimize the event payload for downstream services.</p>
<p>🧪 Test and validate your event-driven workflows to ensure that they are working as expected, and use monitoring tools like CloudWatch to monitor the performance and health of your workflows.</p>
<p>In conclusion, choosing between CloudWatch Events and EventBridge depends on the specific requirements of your application. For example, if you only need basic event triggering and routing, CloudWatch Events might be sufficient. However, if you require more advanced event processing, integration, and scalability, EventBridge is better for managing your AWS Glue Jobs and Step Functions workflows.</p>
<p>Remember to follow best practices and optimize your event-driven workflows for performance and scalability. By choosing exemplary event-driven service and best practices, you can build scalable and resilient applications on AWS.</p>
<p>If you have any inquiries or wish to gain additional knowledge, please get in touch with me on <a target="_blank" href="https://github.com/nextwebb">GitHub</a>, <a target="_blank" href="https://twitter.com/iam_nextwebb">Twitter</a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>. Moreover, kindly show your support by leaving a thumbs up 👍, a comment 💬, and sharing this article with your network 😊.</p>
<h3 id="heading-references">References:</h3>
<p>AWS Documentation, Amazon EventBridge. <a target="_blank" href="https://aws.amazon.com/eventbridge/"><strong>https://aws.amazon.com/eventbridge/</strong></a></p>
<p>AWS Documentation, What is AWS Glue? <a target="_blank" href="https://aws.amazon.com/glue/"><strong>https://aws.amazon.com/glue/</strong></a></p>
<p>AWS Documentation, What is AWS Step Functions? <a target="_blank" href="https://aws.amazon.com/step-functions/"><strong>https://aws.amazon.com/step-functions/</strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with Workflows: An Introduction to State Machines for AWS Step Functions]]></title><description><![CDATA[Introduction📝
Looking to streamline your complex workflows and keep everything on track? Look no further than state machines - a powerful tool for precision and ease in process management. 💪🏼 And when it comes to implementing state machines, AWS S...]]></description><link>https://blog.nextwebb.tech/getting-started-with-workflows-an-introduction-to-state-machines-for-aws-step-functions</link><guid isPermaLink="true">https://blog.nextwebb.tech/getting-started-with-workflows-an-introduction-to-state-machines-for-aws-step-functions</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[serverless]]></category><category><![CDATA[state-machines]]></category><category><![CDATA[stepfunction]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Mon, 27 Feb 2023 21:40:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/VjWi56AWQ9k/upload/74d7f2dc19eddc805725351c0d76f6ec.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction📝</h2>
<p>Looking to streamline your complex workflows and keep everything on track? Look no further than state machines - a powerful tool for precision and ease in process management. 💪🏼 And when it comes to implementing state machines, AWS Step Functions has covered you with a simple JSON-based language and visual representation. 🌟</p>
<p>This article will introduce you to state machines and provide a walkthrough for creating a basic example using AWS Step Functions. You can say goodbye to manual workflow management with automated optimization and enhanced efficiency. 🤖 Are you looking to improve how you handle challenging tasks? Let's get started now! 💻</p>
<h2 id="heading-prerequisites">Prerequisites 🧑‍💻</h2>
<ul>
<li><p>Basic understanding of programming concepts</p>
</li>
<li><p>Some knowledge of AWS services.</p>
</li>
<li><p>Some knowledge of lambdas</p>
</li>
</ul>
<h2 id="heading-the-experiment">The Experiment 😉</h2>
<p>A state machine is a fundamental concept that helps define how a system behaves. It comprises a set of states and the transitions between them. 🚦 These transitions are caused by specific events and follow particular conditions or rules. This feature makes it possible to model different types of processes, like order fulfillment, data pipelines, and machine learning models.</p>
<p>For example, you have an e-commerce website and want to build a state machine for order fulfillment. Your state machine could have states such as "Order Received," "Payment Received," "Order Shipped," and "Order Delivered." 📦 The state machine triggers transitions between these states when it receives events such as "Payment Received" or "Order Shipped." 💳</p>
<p>Now, let's talk about AWS Step Functions. This service allows you to build, run, and visualize state machines using a simple JSON-based language. 🚀 You can use it to model your application's workflow and see a visual representation of the states and transitions. Additionally, Step Functions provides automatic retries and error handling, parallel execution, and the ability to integrate with other AWS services. 🌟</p>
<p>Here's an example of a simple state machine template in JSON for AWS Step Functions:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Comment"</span>: <span class="hljs-string">"A simple example of a state machine"</span>,
  <span class="hljs-attr">"StartAt"</span>: <span class="hljs-string">"Order Received"</span>,
  <span class="hljs-attr">"States"</span>: {
    <span class="hljs-attr">"Order Received"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:OrderReceived"</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Payment Received"</span>
    },
    <span class="hljs-attr">"Payment Received"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:PaymentReceived"</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Order Shipped"</span>
    },
    <span class="hljs-attr">"Order Shipped"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:OrderShipped"</span>,
      <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Order Delivered"</span>
    },
    <span class="hljs-attr">"Order Delivered"</span>: {
      <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
      <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:OrderDelivered"</span>,
      <span class="hljs-attr">"End"</span>: <span class="hljs-literal">true</span>
    }
  }
}
</code></pre>
<p>This template defines a state machine with four states: "Order Received," "Payment Received," "Order Shipped," and "Order Delivered." Each state corresponds to a task implemented by a Lambda function. The state machine starts at "Order Received" and moves through the states in the order specified by the "Next" field. The "Order Delivered" state is an end state, as indicated by the "End" field set to true.</p>
<p>Let's go through creating and running a state machine using Step Functions.</p>
<ol>
<li><p>🏭 Go to the AWS Step Functions Console and create a new state machine.</p>
<p> <img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1677505311/Screen_Shot_2023-02-27_at_11.32.18.png" alt="AWS Step Functions Console" /></p>
</li>
<li><p>🔨 Define your states and the transitions between them using the JSON-based language. You can use the template above as a starting point and customize it to fit your specific use case.</p>
</li>
</ol>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1677505333/Screen_Shot_2023-02-27_at_11.33.30.png" alt="JSON-based states and the transitions" /></p>
<p>You will see the visual representation of the states and transitions and ensure everything works as expected.</p>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1677518211/Screen_Shot_2023-02-27_at_18.16.03.png" alt="visual representation" /></p>
<ol>
<li>✅ After you have reviewed and customized the state machine (ignore the squiggly lines above, the lambda ARNs here are just for testing) to meet your requirements,</li>
</ol>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1677520354/Screen_Shot_2023-02-27_at_18.13.11.png" alt="customized the state machine" /></p>
<p>You can configure the default settings and create an execution role that provides the necessary permissions for the state machine to interact with the resources defined in your workflow. Once confirmed that everything is working as expected, you can initiate the execution of your state machine by triggering its start state.</p>
<p><img src="https://res.cloudinary.com/nextwebb-devs/image/upload/v1677520362/Screen_Shot_2023-02-27_at_18.13.23.png" alt="default settings" /></p>
<ol>
<li><p>🛠️ Create and Test your state machine using the Step Functions Console.</p>
</li>
<li><p>🚀 It's essential to remember that this is a simplified example, and state machines can be much more complex depending on your use case. You can add other states, such as "<a target="_blank" href="https://docs.amazonaws.cn/en_us/step-functions/latest/dg/amazon-states-language-choice-state.html">Choice</a>" or "<a target="_blank" href="https://docs.amazonaws.cn/en_us/step-functions/latest/dg/amazon-states-language-wait-state.html">Wait</a>" states, and specify input and output paths, error handling, and retry options.</p>
</li>
</ol>
<h2 id="heading-in-conclusion">In conclusion,</h2>
<p>State machines are a powerful tool for managing complex workflows, and AWS Step Functions provides a simple and efficient implementation. You can quickly build and state machines using the JSON-based language and the visual representation provided by the console.</p>
<p>🤓 However, remember that this is a complex topic that requires a deep understanding of the concept and the use of AWS Step Functions.</p>
<p>Feel free to contact me on <a target="_blank" href="https://github.com/nextwebb"><strong>GitHub</strong></a>, <a target="_blank" href="https://twitter.com/iam_nextwebb"><strong>Twitter</strong></a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/"><strong>LinkedIn</strong></a> if you have any inquiries or wish to gain additional knowledge. Moreover, kindly show your support by leaving a thumbs up 👍, a comment 💬, and sharing this article with your network 😊.</p>
<h2 id="heading-references">References</h2>
<p>Some helpful resources to get started include the following:</p>
<ul>
<li><p>AWS Step Functions Getting Started Guide: <a target="_blank" href="https://docs.aws.amazon.com/step-functions/latest/dg/getting-started.html"><strong>https://docs.aws.amazon.com/step-functions/latest/dg/getting-started.html</strong></a></p>
</li>
<li><p>AWS Step Functions Developer Guide: <a target="_blank" href="https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html"><strong>https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html</strong></a></p>
</li>
<li><p>AWS Step Functions Sample State Machines: <a target="_blank" href="https://docs.aws.amazon.com/step-functions/latest/dg/sample-state-machines.html"><strong>https://docs.aws.amazon.com/step-functions/latest/dg/sample-state-machines.html</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Automating Prometheus Monitoring on AWS with Ansible and Terraform]]></title><description><![CDATA[👀 Are you tired of manually setting up and configuring Prometheus monitoring on AWS? Look no further than Ansible and Terraform. You can automate the process by utilizing these tools, from provisioning EC2 instances to deploying and configuring Prom...]]></description><link>https://blog.nextwebb.tech/automating-prometheus-monitoring-on-aws-with-ansible-and-terraform</link><guid isPermaLink="true">https://blog.nextwebb.tech/automating-prometheus-monitoring-on-aws-with-ansible-and-terraform</guid><category><![CDATA[AWS]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[ansible]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[monitoring]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Mon, 20 Feb 2023 08:12:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/dBI_My696Rk/upload/cc9e4fea058087b7691a57670820c84f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>👀 Are you tired of manually setting up and configuring Prometheus monitoring on AWS? Look no further than Ansible and Terraform. You can automate the process by utilizing these tools, from provisioning EC2 instances to deploying and configuring Prometheus.</p>
<p>🚀 This tutorial will look at deploying Prometheus monitoring on AWS using Ansible for configuration management and Terraform for infrastructure as code. We will be deploying Prometheus on EC2 instances and behind a load balancer.</p>
<h2 id="heading-prerequisites">🧑‍💻 Prerequisites</h2>
<ul>
<li><p>An AWS account</p>
</li>
<li><p>Basic knowledge of AWS and Terraform</p>
</li>
<li><p>Basic knowledge of Ansible</p>
</li>
<li><p>A text editor</p>
</li>
</ul>
<h2 id="heading-project-overview">🚀 Project Overview</h2>
<p>We will deploy Prometheus monitoring on three EC2 instances behind a load balancer. We will use Terraform to create the EC2 instances, load balancer, and Ansible to configure the instances and deploy Prometheus.</p>
<p>Here is the project directory map:</p>
<p>📁 prometheus-ansible-terraform/</p>
<p>├── 📄 inventory.ini</p>
<p>├── 📄 <a target="_blank" href="http://main.tf">main.tf</a></p>
<p>├── 📄 playbook.yml</p>
<p>├── 📄 prometheus.yml</p>
<p>├── 📄 site.yml</p>
<p>Here are the steps we will take:</p>
<ol>
<li><p>Deploy EC2 instances and a load balancer using Terraform</p>
</li>
<li><p>Configure EC2 instances using Ansible</p>
</li>
<li><p>Install and configure Prometheus on the EC2 instances</p>
</li>
</ol>
<h2 id="heading-terraform-configuration">🔧 Terraform Configuration</h2>
<ol>
<li><p>Create a new directory for your Terraform configuration and create a <a target="_blank" href="http://main.tf"><code>main.tf</code></a> file.</p>
</li>
<li><p>Inside the <a target="_blank" href="http://main.tf"><code>main.tf</code></a> file, define the provider as AWS, and specify the region:</p>
<pre><code class="lang-yaml"> <span class="hljs-string">pythonCopy</span> <span class="hljs-string">codeprovider</span> <span class="hljs-string">"aws"</span> {
   <span class="hljs-string">region</span> <span class="hljs-string">=</span> <span class="hljs-string">"us-east-1"</span>
 }
</code></pre>
</li>
<li><p>Create an EC2 instance and load balancer resource in the <a target="_blank" href="http://main.tf"><code>main.tf</code></a> file:</p>
<pre><code class="lang-yaml"> <span class="hljs-string">resource</span> <span class="hljs-string">"aws_instance"</span> <span class="hljs-string">"prometheus"</span> {
   <span class="hljs-string">ami</span>           <span class="hljs-string">=</span> <span class="hljs-string">"ami-0c94855ba95c71c99"</span>
   <span class="hljs-string">instance_type</span> <span class="hljs-string">=</span> <span class="hljs-string">"t2.micro"</span>
   <span class="hljs-string">count</span>         <span class="hljs-string">=</span> <span class="hljs-number">3</span>

   <span class="hljs-string">tags</span> <span class="hljs-string">=</span> {
     <span class="hljs-string">Name</span> <span class="hljs-string">=</span> <span class="hljs-string">"prometheus"</span>
   }
 }

 <span class="hljs-string">resource</span> <span class="hljs-string">"aws_lb"</span> <span class="hljs-string">"prometheus"</span> {
   <span class="hljs-string">name</span> <span class="hljs-string">=</span> <span class="hljs-string">"prometheus"</span>
   <span class="hljs-string">internal</span> <span class="hljs-string">=</span> <span class="hljs-literal">false</span>

   <span class="hljs-string">listener</span> {
     <span class="hljs-string">lb_port</span> <span class="hljs-string">=</span> <span class="hljs-number">80</span>
     <span class="hljs-string">protocol</span> <span class="hljs-string">=</span> <span class="hljs-string">"HTTP"</span>

     <span class="hljs-string">instance_port</span> <span class="hljs-string">=</span> <span class="hljs-number">9090</span>
     <span class="hljs-string">instance_protocol</span> <span class="hljs-string">=</span> <span class="hljs-string">"HTTP"</span>
   }
 }
</code></pre>
<p> This Terraform configuration will create three EC2 instances and a load balancer. The EC2 instances will be running the Amazon Machine Image (AMI) with ID <code>ami-0c94855ba95c71c99</code>, which is a base Amazon Linux image. We will use this as the base for our Prometheus deployment.</p>
<p> The load balancer will listen on port 80 and forward traffic to port 9090 on the EC2 instances.</p>
</li>
<li><p>Run the following command to initialize the Terraform project:</p>
<pre><code class="lang-bash"> terraform init
 terraform apply
</code></pre>
</li>
</ol>
<h2 id="heading-ansible-configuration">🛠️ Ansible Configuration</h2>
<ol>
<li><p>Create a new directory for your Ansible configuration and create a <code>inventory.ini</code> file. Inside the file, specify the EC2 public IP addresses provisioned by the terraform code:</p>
<pre><code class="lang-bash"> [prometheus]
 ec2-x-x-x-x.compute-1.amazonaws.com
 ec2-y-y-y-y.compute-1.amazonaws.com
 ec2-z-z-z-z.compute-1.amazonaws.com
</code></pre>
</li>
<li><p>Create a <code>site.yml</code> file with the following playbook that configures the EC2 instances:</p>
<pre><code class="lang-yaml"> <span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">prometheus</span>
   <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
   <span class="hljs-attr">tasks:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">necessary</span> <span class="hljs-string">packages</span>
       <span class="hljs-attr">yum:</span>
         <span class="hljs-attr">name:</span>
           <span class="hljs-bullet">-</span> <span class="hljs-string">wget</span>
           <span class="hljs-bullet">-</span> <span class="hljs-string">git</span>
           <span class="hljs-bullet">-</span> <span class="hljs-string">unzip</span>
           <span class="hljs-bullet">-</span> <span class="hljs-string">java-1.8.0-openjdk</span>
         <span class="hljs-attr">state:</span> <span class="hljs-string">present</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">and</span> <span class="hljs-string">install</span> <span class="hljs-string">node_exporter</span>
       <span class="hljs-attr">get_url:</span>
         <span class="hljs-attr">url:</span> <span class="hljs-string">https://github.com/prometheus/node_exporter/releases/download/v1.0.1/node_exporter-1.0.1.linux-amd64.tar.gz</span>
         <span class="hljs-attr">dest:</span> <span class="hljs-string">/tmp/node_exporter.tar.gz</span>
       <span class="hljs-attr">become_user:</span> <span class="hljs-string">ec2-user</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Extract</span> <span class="hljs-string">node_exporter</span>
       <span class="hljs-attr">unarchive:</span>
         <span class="hljs-attr">src:</span> <span class="hljs-string">/tmp/node_exporter.tar.gz</span>
         <span class="hljs-attr">dest:</span> <span class="hljs-string">/usr/local/bin/</span>
         <span class="hljs-attr">remote_src:</span> <span class="hljs-literal">true</span>
         <span class="hljs-attr">copy:</span> <span class="hljs-literal">no</span>
       <span class="hljs-attr">become_user:</span> <span class="hljs-string">root</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Configure</span> <span class="hljs-string">node_exporter</span>
       <span class="hljs-attr">template:</span>
         <span class="hljs-attr">src:</span> <span class="hljs-string">templates/node_exporter.service.j2</span>
         <span class="hljs-attr">dest:</span> <span class="hljs-string">/etc/systemd/system/node_exporter.service</span>
         <span class="hljs-attr">owner:</span> <span class="hljs-string">root</span>
         <span class="hljs-attr">group:</span> <span class="hljs-string">root</span>
         <span class="hljs-attr">mode:</span> <span class="hljs-string">'0644'</span>
       <span class="hljs-attr">notify:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">reload</span> <span class="hljs-string">systemd</span>
</code></pre>
</li>
<li><p>Create a <code>prometheus.yml</code> file that defines the Prometheus configuration:</p>
</li>
</ol>
<pre><code class="lang-yaml">   <span class="hljs-attr">global:</span>
     <span class="hljs-attr">scrape_interval:</span> <span class="hljs-string">5s</span>

   <span class="hljs-attr">scrape_configs:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">ec2-instances</span>
       <span class="hljs-attr">ec2_sd_configs:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>
           <span class="hljs-attr">port:</span> <span class="hljs-number">9100</span>
       <span class="hljs-attr">relabel_configs:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">source_labels:</span> [<span class="hljs-string">__meta_ec2_tag_Name</span>]
           <span class="hljs-attr">target_label:</span> <span class="hljs-string">instance</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">source_labels:</span> [<span class="hljs-string">__meta_ec2_private_ip</span>]
           <span class="hljs-attr">target_label:</span> <span class="hljs-string">ip</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">job_name:</span> <span class="hljs-string">load-balancers</span>
       <span class="hljs-attr">ec2_sd_configs:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">region:</span> <span class="hljs-string">us-east-1</span>
           <span class="hljs-attr">port:</span> <span class="hljs-number">9100</span>
           <span class="hljs-attr">filters:</span>
             <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"tag:Component"</span>
               <span class="hljs-attr">values:</span> [<span class="hljs-string">"LoadBalancer"</span>]
       <span class="hljs-attr">relabel_configs:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">source_labels:</span> [<span class="hljs-string">__meta_ec2_tag_Name</span>]
           <span class="hljs-attr">target_label:</span> <span class="hljs-string">load_balancer</span>
         <span class="hljs-bullet">-</span> <span class="hljs-attr">source_labels:</span> [<span class="hljs-string">__meta_ec2_private_ip</span>]
           <span class="hljs-attr">target_label:</span> <span class="hljs-string">ip</span>
</code></pre>
<p>This configuration file specifies two scrape jobs: one for EC2 instances and one for load balancers. The EC2 job uses EC2 service discovery to dynamically discover all running instances and scrape their metrics using the Node Exporter on port 9100.</p>
<p>The load balancer job filters by instances with a Component tag set to LoadBalancer.</p>
<p>Next, let's create an Ansible playbook to install and configure Prometheus on our EC2 instances. We'll create a file named <code>playbook.yml</code> in the same directory as our <code>site.yml</code> playbook.</p>
<ol>
<li><p>Create a new Ansible playbook in a file named <code>playbook.yml</code> that installs and configures Prometheus:</p>
<pre><code class="lang-yaml"> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Prometheus</span>
   <span class="hljs-attr">hosts:</span> <span class="hljs-string">prometheus</span>
   <span class="hljs-attr">become:</span> <span class="hljs-literal">true</span>
   <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>

   <span class="hljs-attr">tasks:</span>
     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Prometheus</span>
       <span class="hljs-attr">apt:</span>
         <span class="hljs-attr">name:</span> <span class="hljs-string">prometheus</span>
         <span class="hljs-attr">state:</span> <span class="hljs-string">present</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">Prometheus</span> <span class="hljs-string">configuration</span> <span class="hljs-string">file</span>
       <span class="hljs-attr">template:</span>
         <span class="hljs-attr">src:</span> <span class="hljs-string">prometheus.yml</span>
         <span class="hljs-attr">dest:</span> <span class="hljs-string">/etc/prometheus/prometheus.yml</span>

     <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Start</span> <span class="hljs-string">Prometheus</span> <span class="hljs-string">service</span>
       <span class="hljs-attr">systemd:</span>
         <span class="hljs-attr">name:</span> <span class="hljs-string">prometheus</span>
         <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
         <span class="hljs-attr">enabled:</span> <span class="hljs-literal">yes</span>
</code></pre>
</li>
<li><p>Run the <code>site.yml</code> playbook to configure the EC2 instances:</p>
<pre><code class="lang-bash"> ansible-playbook -i inventory.ini site.yml
</code></pre>
</li>
<li><p>Run the <code>playbook.yml</code> playbook to install and configure Prometheus:</p>
<pre><code class="lang-bash"> ansible-playbook -i inventory.ini playbook.yml
</code></pre>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion🎉</h2>
<p>In this tutorial, we learned how to use Terraform to deploy an infrastructure on AWS that includes EC2 instances, an Application Load Balancer, and a Prometheus server. We also learned how to use Ansible to configure and manage the Prometheus server. With these tools, we can automate the deployment and management of our infrastructure and ensure that our Prometheus monitoring system is up and running smoothly.</p>
<p>This is a basic example, but you can do many other things with Terraform and Ansible to manage your infrastructure and automate your workflows. By mastering these tools, you can build robust and scalable cloud-based systems that meet the needs of your business or organization.</p>
<p>Don't hesitate to reach out to me on <a target="_blank" href="https://github.com/nextwebb"><strong>GitHub</strong></a>, <a target="_blank" href="https://twitter.com/iam_nextwebb"><strong>Twitter</strong></a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/"><strong>LinkedIn</strong></a> if you have any questions or want to learn more. And don't forget to give a thumbs up 👍, leave a comment 💬 and share this article with your network 😊.</p>
<h2 id="heading-references">References:</h2>
<ul>
<li><p>Terraform documentation: <a target="_blank" href="https://www.terraform.io/docs/index.html"><strong>https://www.terraform.io/docs/index.html</strong></a></p>
</li>
<li><p>Ansible documentation: <a target="_blank" href="https://docs.ansible.com/"><strong>https://docs.ansible.com/</strong></a></p>
</li>
<li><p>Prometheus documentation: <a target="_blank" href="https://prometheus.io/docs/introduction/overview/"><strong>https://prometheus.io/docs/introduction/overview/</strong></a></p>
</li>
<li><p>AWS documentation: <a target="_blank" href="https://aws.amazon.com/documentation/"><strong>https://aws.amazon.com/documentation/</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[🚀 Simplifying Serverless Development: A Comparison of Step Functions, Chalice and Python Async Methods]]></title><description><![CDATA[Comparing Serverless Development with Step Functions, Chalice, and Python Async Methods
Introduction📝
When it comes to building serverless applications, several options are available for coordinating and executing tasks. AWS Step Functions 🤖 and th...]]></description><link>https://blog.nextwebb.tech/simplifying-serverless-development-a-comparison-of-step-functions-chalice-and-python-async-methods</link><guid isPermaLink="true">https://blog.nextwebb.tech/simplifying-serverless-development-a-comparison-of-step-functions-chalice-and-python-async-methods</guid><category><![CDATA[stepfunction]]></category><category><![CDATA[Python]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Wed, 25 Jan 2023 16:37:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674664144246/d33ed53f-4e16-454d-b577-bcc990150430.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Comparing Serverless Development with Step Functions, Chalice, and Python Async Methods</em></p>
<h3 id="heading-introduction">Introduction📝</h3>
<p>When it comes to building serverless applications, several options are available for coordinating and executing tasks. AWS Step Functions 🤖 and the Chalice framework 🛠️ are two popular choices, but Python also provides built-in support for asynchronous programming through the async and await keywords 🔑. In this article, we will compare these three options and explore when to use each one to build scalable and maintainable serverless applications using Python 🐍.</p>
<h3 id="heading-prerequisites">Prerequisites 🧑‍💻</h3>
<p>This article assumes that you have the following:</p>
<ul>
<li><p>Some essential knowledge of Python</p>
</li>
<li><p>Know what is AWS step functions and lambda</p>
</li>
<li><p>Are you familiar with serverless applications</p>
</li>
</ul>
<h3 id="heading-discussions">Discussions 💬</h3>
<p>AWS Step Functions is a service that allows you to create and run workflows that coordinate multiple AWS services, such as Lambda functions, DynamoDB tables, and SQS queues. These workflows, known as state machines, are defined using the JSON-based Amazon States Language. Step Functions provides a powerful way to visualize and understand the flow of your application, which can help with troubleshooting and debugging 🔍. However, it does add a layer of complexity, as you need to manage the state machine and its associated resources 🤔.</p>
<p>For example, consider a simple workflow that processes an image file uploaded to an S3 bucket. The workflow could consist of the following steps:</p>
<p>1). First, a Lambda function is triggered by the S3 event and resizes the image.📷</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3

s3 = boto3.client(<span class="hljs-string">'s3'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">resize_image</span>(<span class="hljs-params">event, context</span>):</span>
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to resize image</span>
    s3.put_object(Bucket=bucket, Key=key, Body=resized_image)
</code></pre>
<p>2). Another Lambda function is triggered by the first one and applies the watermark to the image.💦</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3

s3 = boto3.client(<span class="hljs-string">'s3'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">apply_watermark</span>(<span class="hljs-params">event, context</span>):</span>
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to apply watermark</span>
    s3.put_object(Bucket=bucket, Key=key, Body=watermarked_image)
</code></pre>
<p>3). A final Lambda function that stores the processed image in a different S3 bucket.📁</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3

s3 = boto3.client(<span class="hljs-string">'s3'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">store_image</span>(<span class="hljs-params">event, context</span>):</span>
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to get the processed image</span>
    s3.put_object(Bucket=<span class="hljs-string">'processed-images'</span>, Key=key, Body=processed_image)
</code></pre>
<h3 id="heading-using-step-functions">Using step functions 🚀🧭</h3>
<p>With Step Functions, you can define these steps in a state machine 📈 and have them executed in the specified order, with error handling 🤕 and retries built-in. This can significantly simplify the development process 🛠️ and make it easier to manage the application 💼.</p>
<p>Here is an example of a simple state machine for the Step Functions workflow:</p>
<pre><code class="lang-json">{
<span class="hljs-attr">"Comment"</span>: <span class="hljs-string">"A simple workflow that processes an image file uploaded to an S3 bucket"</span>,
<span class="hljs-attr">"StartAt"</span>: <span class="hljs-string">"Resize Image"</span>,
<span class="hljs-attr">"States"</span>: {
  <span class="hljs-attr">"Resize Image"</span>: {
    <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
    <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:resize_image"</span>,
    <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Apply Watermark"</span>
  },
  <span class="hljs-attr">"Apply Watermark"</span>: {
    <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
    <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:apply_watermark"</span>,
    <span class="hljs-attr">"Next"</span>: <span class="hljs-string">"Store Image"</span>
  },
  <span class="hljs-attr">"Store Image"</span>: {
    <span class="hljs-attr">"Type"</span>: <span class="hljs-string">"Task"</span>,
    <span class="hljs-attr">"Resource"</span>: <span class="hljs-string">"arn:aws:lambda:REGION:ACCOUNT_ID:function:store_image"</span>,
    <span class="hljs-attr">"End"</span>: <span class="hljs-literal">true</span>
  }
}
}
</code></pre>
<p>The above code block is an example of a state machine for the Step Functions workflow. It defines a simple workflow that processes an image file uploaded to an S3 bucket. The workflow consists of three steps: "Resize Image", "Apply Watermark", and "Store Image". Each step is represented by a state in the state machine and is executed in the specified order.</p>
<h3 id="heading-using-chalice-and-python-async-methods">Using Chalice and Python Async methods 🔥🐍💻</h3>
<p>It provides a simple, intuitive way to define and deploy Lambda functions 💻 and configure the associated resources, such as API Gateway endpoints 🌐 and IAM roles 🔒.</p>
<p>Chalice makes it easy to start serverless development 🛠, as it abstracts away many of the complexities of working with AWS services directly.</p>
<p>Here is an example of how the same example can be coordinated/invoked within Chalice and Python async methods:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> chalice <span class="hljs-keyword">import</span> Chalice, Response
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> boto3
<span class="hljs-keyword">import</span> asyncio

app = Chalice(app_name=<span class="hljs-string">'image-processing'</span>)
s3 = boto3.client(<span class="hljs-string">'s3'</span>)

<span class="hljs-meta">@app.lambda_function()</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">resize_image</span>(<span class="hljs-params">event, context</span>):</span>
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to resize image</span>
    s3.put_object(Bucket=bucket, Key=key, Body=resized_image)
    apply_watermark.apply(event, context)

<span class="hljs-meta">@app.lambda_function()</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">apply_watermark</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">1</span>)
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to apply watermark</span>
    s3.put_object(Bucket=bucket, Key=key, Body=watermarked_image)
    store_image.apply(event, context)

<span class="hljs-meta">@app.lambda_function()</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">store_image</span>(<span class="hljs-params">event, context</span>):</span>
    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">1</span>)
    bucket = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'bucket'</span>][<span class="hljs-string">'name'</span>]
    key = event[<span class="hljs-string">'Records'</span>][<span class="hljs-number">0</span>][<span class="hljs-string">'s3'</span>][<span class="hljs-string">'object'</span>][<span class="hljs-string">'key'</span>]
    <span class="hljs-comment"># code to get the processed image</span>
    s3.put_object(Bucket=<span class="hljs-string">'processed-images'</span>, Key=key, Body=processed_image)
</code></pre>
<p>In the above example, the <code>resize_image</code> function is invoked by the S3 event, and once it completes the execution, the apply_watermark function is called, and the same goes for the last function. Chalice framework handles the invocation of these functions and orchestrates them together.</p>
<p>🤓 If you're looking to build a serverless application using Python, you have several options! One popular option is AWS Step Functions, which allows you to easily coordinate multiple AWS services and simplify the development process for complex applications. Another option is the Chalice framework, which provides a simple and intuitive way to define and deploy Lambda functions and configure resources such as API Gateway endpoints and IAM roles.</p>
<p>💻 But perhaps the most exciting option is using Python's built-in support for asynchronous programming through the async and await keywords. This allows you to write non-blocking code, handling multiple tasks simultaneously, rather than waiting for one task to complete before starting the next. You have more control over the flow of your application and can take advantage of built-in libraries such as <code>asyncio</code> and <code>aiohttp</code>. However, it requires more expertise and experience with asynchronous programming, and you will need to handle things like scaling and error handling yourself.</p>
<p>🤔 Ultimately, the best choice will depend on the specific requirements of your application and your team's expertise. The Chalice framework may be the best choice if you build a simple application and want to start quickly. If you need more control over the flow of your application and are comfortable with asynchronous programming, then Python async methods may be a better fit. If you are building a complex application with multiple steps and decision points, AWS Step Functions can help simplify the development process.</p>
<p>💡 It's also worth noting that these three approaches are not mutually exclusive, and you can combine them to build more complex applications. For example, you could use Step Functions to coordinate the flow of your application and use Chalice and Python async methods to handle the logic of your application.</p>
<p>🚀 So, what are you waiting for? Take the first step towards building your dream serverless application using Python today! 🚀</p>
<h3 id="heading-conclusion">Conclusion🤔</h3>
<p>Building serverless applications with Python is a breeze with AWS Step Functions 🚀, the Chalice framework 🛠️, and Python async methods 💻. Each of these tools has its unique strengths and can be used to achieve different goals.</p>
<p>AWS Step Functions makes it super simple to coordinate and execute multiple Lambda functions in a specific order📝. At the same time, the Chalice framework takes the complexity out of deploying and configuring Lambda functions and other resources🌎. With Python async methods, you can build ultra-efficient and scalable applications that can handle high levels of concurrency🔥. Whether you're just getting started with serverless or are a seasoned pro, these tools are the perfect choice for your next project! So what are you waiting for? Give them a try and see the magic for yourself🌟🚀.</p>
<p>Don't hesitate to reach out to me on <a target="_blank" href="https://github.com/nextwebb"><strong>GitHub</strong></a>, <a target="_blank" href="https://twitter.com/iam_nextwebb"><strong>Twitter</strong></a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/"><strong>LinkedIn</strong></a> if you have any questions or want to learn more. And don't forget to give a thumbs up, leave a comment and share this article with your network 😊.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p>AWS Step Functions: <a target="_blank" href="https://aws.amazon.com/step-functions/"><strong>https://aws.amazon.com/step-functions/</strong></a></p>
</li>
<li><p>Chalice: <a target="_blank" href="https://aws.github.io/chalice/"><strong>https://aws.github.io/chalice/</strong></a></p>
</li>
<li><p>Python's built-in support for asynchronous programming: <a target="_blank" href="https://docs.python.org/3/library/asyncio.html"><strong>https://docs.python.org/3/library/asyncio.html</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Create Docker Containers Using Ansible 🐳🚀:  Introduction]]></title><description><![CDATA[🤔 Are you tired of manually setting up and configuring your Docker containers? 🙅‍♂️ Say no more! 🚀 With Ansible, you can automate the creation of Docker containers in just a few simple steps.
But first, what is Ansible? 🤔 Ansible is an open-sourc...]]></description><link>https://blog.nextwebb.tech/create-docker-containers-using-ansible-introduction</link><guid isPermaLink="true">https://blog.nextwebb.tech/create-docker-containers-using-ansible-introduction</guid><category><![CDATA[containerization]]></category><category><![CDATA[Docker]]></category><category><![CDATA[ansible]]></category><category><![CDATA[automation]]></category><category><![CDATA[#devops #jenkins #docker #integration #ci/cd]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Tue, 10 Jan 2023 08:25:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/hKJTmtrhHGA/upload/0f20f777c374a06949e980217356d36b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>🤔 Are you tired of manually setting up and configuring your Docker containers? 🙅‍♂️ Say no more! 🚀 With Ansible, you can automate the creation of Docker containers in just a few simple steps.</p>
<p>But first, what is Ansible? 🤔 Ansible is an open-source automation platform that allows you to automate configuration management, application deployment, and orchestration tasks. It is simple to use and does not require an agent or additional custom security infrastructure, making it an ideal choice for automation. This article will walk you through the process of quickly automating Docker container creation using Ansible.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>This article assumes that you have 😉:</p>
<ul>
<li><p>Some essential experience with <a target="_blank" href="https://docs.docker.com/get-started/">Docker</a></p>
</li>
<li><p>Know what a playbook is and how to create one in <a target="_blank" href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html">Ansible</a></p>
</li>
<li><p>Are familiar with <a target="_blank" href="https://www.jenkins.io/doc/book/pipeline/getting-started/#snippet-generator">Jenkins</a> and how to set up a workflow</p>
</li>
</ul>
<h3 id="heading-lets-dive-into-a-real-world-use-case-to-see-how-ansible-can-make-our-lives-easier"><strong>Let's dive into a real-world use case to see how Ansible can make Our lives easier. 🌎</strong></h3>
<p>A load balancer is like a 🚨 traffic cop directing people to the right server when they want to use a web app. To ensure the app works for everyone, we have to ensure each server has the correct version of the app and the right dependencies installed. We can use a tool like Ansible to set everything up consistently on all the servers. 💻💾🧑💻💾🧑💻💾🧑</p>
<p>Now, look at code samples to see how we can use Ansible to create Docker containers. 📝</p>
<p>First, we need to install the Docker python module and the ansible Docker module:</p>
<pre><code class="lang-bash">pip install docker
pip install ansible[docker]
</code></pre>
<p>Next, we can create a <a target="_blank" href="https://www.redhat.com/en/topics/automation/what-is-an-ansible-playbook#:~:text=Ansible%20Playbooks%20are%20lists%20of,as%20which%20user%20executes%20it.">playbook</a> <code>&lt;playbook_name&gt;.yml</code> that uses the <code>docker_container</code> Module to create a Docker container. 🐳 Here is an example of how to do this:</p>
<pre><code class="lang-javascript">   - name: Create Docker container 🐳
      <span class="hljs-attr">hosts</span>: localhost
      <span class="hljs-attr">tasks</span>:
        - name: Pull image <span class="hljs-keyword">from</span> DockerHub 📥
          <span class="hljs-attr">docker_image</span>:
            name: ubuntu
            <span class="hljs-attr">source</span>: pull
        - name: Create Docker container 📦
          <span class="hljs-attr">docker_container</span>:
            name: ubuntu_container
            <span class="hljs-attr">image</span>: ubuntu
            <span class="hljs-attr">state</span>: started
</code></pre>
<p>This playbook will pull the latest version of the Ubuntu image from DockerHub and create a Docker container based on that image. Finally, we will give the container the name <code>ubuntu_container</code> and start it.</p>
<p>You can customize the container by adding additional arguments to the <code>docker_container</code> Module. For example, you can specify the command to run when the container is started or mount a volume to the container.</p>
<p>To run the playbook, you can then run the playbook using the ansible-playbook command:</p>
<pre><code class="lang-bash">ansible-playbook &lt;playbook_name&gt;.yml
</code></pre>
<p>We can integrate this playbook into a Jenkins workflow to simplify the process. This way, we can trigger the playbook to automatically run whenever certain events occur, such as a code change or a build completion.</p>
<p>To integrate this playbook into a Jenkins workflow, you can do the following:</p>
<ol>
<li><p>Install the Ansible plugin in Jenkins. It will allow you to run Ansible playbooks as part of a Jenkins job.</p>
</li>
<li><p>Create a new Jenkins pipeline job by defining a pipeline script in a <code>Jenkinsfile</code>.</p>
</li>
<li><p>In the pipeline definition, specify the location of your playbook file and any required variables.</p>
<p> <em>For example, below is</em> <code>JenkinsFile</code> <em>defining each stage of the pipeline job :</em></p>
</li>
</ol>
<pre><code class="lang-javascript">pipeline {
  agent {
    label <span class="hljs-string">'my-ansible-agent'</span>
  }
  stages {
    stage(<span class="hljs-string">'Create Docker container'</span>) {
      steps {
        ansiblePlaybook(
          playbook: <span class="hljs-string">'create_docker_container.yml'</span>, 
          <span class="hljs-attr">extraVars</span>: [
            containerName: <span class="hljs-string">'ubuntu_container'</span>,
            <span class="hljs-attr">imageName</span>: <span class="hljs-string">'ubuntu'</span>
          ]
        )
      }
    }
  }
}
</code></pre>
<ol>
<li>Run the Jenkins job to execute the playbook.</li>
</ol>
<p>Note that you will need to have the Docker python module and the ansible Docker module installed on the Jenkins agent machine for the playbook to run successfully. You can do this by adding the following steps to your pipeline definition:</p>
<pre><code class="lang-javascript">stage(<span class="hljs-string">'Install dependencies'</span>) {
  steps {
    sh <span class="hljs-string">'pip install docker'</span>
    sh <span class="hljs-string">'pip install ansible[docker]'</span>
  }
}
</code></pre>
<p>It will install the required dependencies before the playbook runs.</p>
<p>In summary, Ansible is a powerful tool that allows you to automate the creation of Docker containers 🚀 . In addition, you can easily set up and configure multiple Docker containers by creating playbooks 📦 . So next time you create Docker containers, give Ansible a try 💪!</p>
<p>I hope you enjoyed reading this article and found it helpful 🤓, as much as I did writing it 😊! You can find me on <a target="_blank" href="https://github.com/nextwebb">GitHub</a>, <a target="_blank" href="https://twitter.com/iam_nextwebb">Twitter</a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a> if you have any questions or want to learn more. Don't forget to show your support by giving this article a like, leaving a comment, and sharing it with your friends and followers 😊.</p>
<p>Here are some useful references to check out:</p>
<ul>
<li><p><a target="_blank" href="https://docs.ansible.com/"><strong>Ansible documentation</strong></a></p>
</li>
<li><p><a target="_blank" href="https://pypi.org/project/docker/"><strong>Docker python module</strong></a></p>
</li>
<li><p><a target="_blank" href="https://docs.ansible.com/ansible/latest/modules/docker_container_module.html"><strong>Ansible Docker module</strong></a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Why is idempotency very critical in APIs?]]></title><description><![CDATA[Idempotence is an essential concept in APIs; it allows clients to make similar requests without causing problems or changing the system's state💡.
For instance, consider an API that has a "create user" endpoint. Suppose this endpoint is not idempoten...]]></description><link>https://blog.nextwebb.tech/why-is-idempotency-very-critical-in-apis</link><guid isPermaLink="true">https://blog.nextwebb.tech/why-is-idempotency-very-critical-in-apis</guid><category><![CDATA[API Design]]></category><category><![CDATA[REST API]]></category><category><![CDATA[idempotence]]></category><category><![CDATA[http]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Wed, 04 Jan 2023 12:11:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/2fc6ee650a51337d427fb8fc84cb4f26.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Idempotence is an essential concept in APIs; it allows clients to make similar requests without causing problems or changing the system's state💡.</p>
<p>For instance, consider an API that has a "create user" endpoint. Suppose this endpoint is not idempotent, and a client inadvertently makes the same request twice. In that case, the API may create two duplicate user accounts, which could lead to data inconsistencies or other problems 🤯. On the contrary, if the endpoint is idempotent, the API will recognize that the request has already been completed and will not create a duplicate user account 😌 .</p>
<p>It is generally good practice to design APIs to be idempotent wherever possible, as this can make them more predictable and easier to use 💪.</p>
<h3 id="heading-here-are-some-reasons-why-idempotence-is-important-in-apis">Here are some reasons why idempotence is important in APIs: 🤔</h3>
<ol>
<li><p><strong>Preventing unintended side effects</strong>: If an API endpoint is not idempotent and a client makes the same request multiple times, it could potentially cause unintended side effects, such as creating duplicate records in a database 🚫. For example, consider the following code for a non-idempotent "create user" endpoint in Node.js 💻:</p>
<pre><code class="lang-javascript"> app.post(<span class="hljs-string">"/users"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   <span class="hljs-keyword">const</span> user = req.body;
   User.create(user, <span class="hljs-function">(<span class="hljs-params">err, newUser</span>) =&gt;</span> {
     <span class="hljs-keyword">if</span> (err) {
       res.status(<span class="hljs-number">500</span>).send(err);
     } <span class="hljs-keyword">else</span> {
       res.status(<span class="hljs-number">201</span>).send(newUser);
     }
   });
 });
</code></pre>
<p> If a client inadvertently makes the same request twice, this endpoint will create two identical user records in the database, which could lead to data inconsistencies 😟.</p>
<p> Here is an example of how you could modify the code snippet to make it idempotent:</p>
<pre><code class="lang-javascript"> app.post(<span class="hljs-string">"/users"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   <span class="hljs-keyword">const</span> user = req.body;
   User.findOne({ <span class="hljs-attr">email</span>: user.email }, <span class="hljs-function">(<span class="hljs-params">err, existingUser</span>) =&gt;</span> {
     <span class="hljs-keyword">if</span> (err) {
       res.status(<span class="hljs-number">500</span>).send(err); 💥
     } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (existingUser) {
       res.status(<span class="hljs-number">200</span>).send(existingUser); 🟢
     } <span class="hljs-keyword">else</span> {
       User.create(user, <span class="hljs-function">(<span class="hljs-params">err, newUser</span>) =&gt;</span> {
         <span class="hljs-keyword">if</span> (err) {
           res.status(<span class="hljs-number">500</span>).send(err); 💥
         } <span class="hljs-keyword">else</span> {
           res.status(<span class="hljs-number">201</span>).send(newUser); 🟢
         }
       });
     }
   });
 });
</code></pre>
<p> This version of the code checks if the user already exists in DB. Returns existing user with 200 (OK) status code if it does. Creates new user and returns with 201 (Created) status code if it doesn't. 🔄 Prevents multiple copies of the same user in DB from identical requests💡.</p>
</li>
<li><p><strong>Simplifying client code</strong>: If an API endpoint is idempotent, clients can make multiple, indistinguishable requests without having to worry about generating unintended side effects. 💡 This can simplify the client code, as the client does not need to track the system's state or handle potential error cases 💪 .</p>
</li>
<li><p><strong>Improving reliability</strong>: Idempotent APIs are more reliable because clients can retry failed requests without worrying about altering the system's state. 🔄 For example, consider the following idempotent code for a "transfer funds" endpoint 💰:</p>
<pre><code class="lang-javascript"> app.patch(<span class="hljs-string">"/transfer"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   <span class="hljs-keyword">const</span> transfer = req.body;
   Transfer.findOneAndUpdate({ <span class="hljs-attr">_id</span>: transfer.id }, transfer, { <span class="hljs-attr">upsert</span>: <span class="hljs-literal">true</span> }, <span class="hljs-function">(<span class="hljs-params">err, updatedTransfer</span>) =&gt;</span> {
     <span class="hljs-keyword">if</span> (err) {
       res.status(<span class="hljs-number">500</span>).send(err);
     } <span class="hljs-keyword">else</span> {
       res.status(<span class="hljs-number">200</span>).send(updatedTransfer);
     }
   });
 });
</code></pre>
<p> If the client's initial request fails due to a network error, it can retry the request without worrying about transferring the funds multiple times 💻 .</p>
</li>
<li><p><strong>Ensuring consistency</strong>: Idempotent APIs can help ensure data consistency because clients can retry failed requests without altering the system's state. 💪 For example, consider the following idempotent code for a "create order" endpoint 🛒 :</p>
<pre><code class="lang-javascript"> codeapp.post(<span class="hljs-string">"/orders"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
   <span class="hljs-keyword">const</span> order = req.body;
   Order.findOneAndUpdate({ <span class="hljs-attr">_id</span>: order.id }, order, { <span class="hljs-attr">upsert</span>: <span class="hljs-literal">true</span> }, <span class="hljs-function">(<span class="hljs-params">err, updatedOrder</span>) =&gt;</span> {
     <span class="hljs-keyword">if</span> (err) {
       res.status(<span class="hljs-number">500</span>).send(err);
     } <span class="hljs-keyword">else</span> {
       res.status(<span class="hljs-number">200</span>).send(updatedOrder);
     }
   });
 });
</code></pre>
<p> If the client's initial request fails due to a network error, it can retry the request without worrying about creating multiple identical orders 💻 .</p>
</li>
<li><p><strong>Improving performance</strong> means that if a request fails, a client can retry it without unintended consequences. It can help improve the API's performance by reducing the server's workload and making it more responsive. So if you have an idempotent API, you don't have to worry about duplicate actions or changes to the system's state when you retry a failed request. Pretty cool, right? 🤓</p>
</li>
</ol>
<h3 id="heading-queue-systems-can-help">Queue systems can help 🤔!</h3>
<p>In queue systems, idempotency 🤝 is used to ensure that each message 💬 in the queue is processed only once 🔁, even if the consumer receives the message multiple times 🔄. It is essential because it prevents duplicate messages 📩 from being processed and causing unintended consequences 🤯, such as creating duplicate records 📊 or triggering multiple actions 🤔.</p>
<p>Here is an example 💡 of how idempotency can be implemented in a queue system using BullMQ, a popular queue library for Node.js 💻</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Bull = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bullmq'</span>);

<span class="hljs-keyword">const</span> queue = <span class="hljs-keyword">new</span> Bull(<span class="hljs-string">'payment-queue'</span>);

queue.process(<span class="hljs-keyword">async</span> (job) =&gt; {
  <span class="hljs-keyword">const</span> paymentId = job.data.paymentId;
  <span class="hljs-keyword">const</span> payment = <span class="hljs-keyword">await</span> Payment.findById(paymentId);

  <span class="hljs-keyword">if</span> (!payment) {
    <span class="hljs-comment">// Payment not found, process the payment</span>
    <span class="hljs-keyword">await</span> processPayment(paymentId);
  }

  <span class="hljs-comment">// Return a promise that resolves to remove the job from the queue</span>
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve();
});
</code></pre>
<p>The consumer checks the database 🗃️ to see if they have already processed a payment. If the payment is already processed, the consumer removes the job from the queue without further action. It ensures that each payment is processed only once, even if the consumer receives the message multiple times.</p>
<h3 id="heading-in-conclusion">In conclusion, 💡</h3>
<p>In addition to improving reliability 💪 and data consistency 🧐, idempotency in queue systems can also improve performance 📈; by reducing the number of duplicate messages that need to be processed. In addition, it can help reduce the system's overall workload, making it more efficient and scalable 📈.</p>
<p>Overall, idempotency is an essential property in queue systems that helps ensure the system's reliability, consistency, and performance. By designing APIs to be idempotent wherever possible, you can help to ensure that your system can handle multiple identical requests without causing unintended consequences.</p>
<p>Thank you for reading this article! I hope you found it helpful 🤓. If you have any questions or want to learn more, feel free to reach out to me on <a target="_blank" href="https://github.com/nextwebb"><strong>GitHub</strong></a>, <a target="_blank" href="https://twitter.com/iam_nextwebb"><strong>Twitter</strong></a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/"><strong>LinkedIn</strong></a>. Also, don't forget to give this article a like, leave a comment, and share it with your network 😊 .</p>
<p>For further reading on the topic of idempotency in APIs, you may want to check out the following resources:</p>
<p>"HTTP Idempotent Methods" (<a target="_blank" href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html"><strong>https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html</strong></a>)</p>
<p>"RESTful API Designing guidelines - The best practices" (<a target="_blank" href="https://www.slideshare.net/RestApiTutorial/restful-api-designing-guidelines-the-best-practices"><strong>https://www.slideshare.net/RestApiTutorial/restful-api-designing-guidelines-the-best-practices</strong></a>)</p>
<p>"API Idempotence - What Is It and Why Is It Important?" 🤔(<a target="_blank" href="https://www.moesif.com/blog/technical/api-idempotence/"><strong>https://www.moesif.com/blog/technical/api-idempotence/</strong></a>)</p>
<p>"Idempotent jobs" (<a target="_blank" href="https://docs.bullmq.io/patterns/idempotent-jobs">https://docs.bullmq.io/patterns/idempotent-jobs</a>)</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Virtual Private Cloud (VPC) with AWS CloudFormation]]></title><description><![CDATA[The DevOps process and tooling have continued to evolve, as building on the cloud becomes a growing need for products that require scale and without worrying about physical infrastructure. You only pay for the resources you use such as only paying wh...]]></description><link>https://blog.nextwebb.tech/creating-a-virtual-private-cloud-vpc-with-aws-cloudformation</link><guid isPermaLink="true">https://blog.nextwebb.tech/creating-a-virtual-private-cloud-vpc-with-aws-cloudformation</guid><category><![CDATA[AWS]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Cloud]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Mon, 24 Jan 2022 09:41:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Im_cQ6hQo10/upload/v1642987191606/iCX2UflSs.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The DevOps process and tooling have continued to evolve, as building on the cloud becomes a growing need for products that require scale and without worrying about physical infrastructure. You only pay for the resources you use such as only paying when your code runs. 
Simply put; the benefits of the cloud while several include:</p>
<ul>
<li>Stop guessing about capacity.</li>
<li>Avoid huge capital investments upfront.</li>
<li>Pay for only what you use.</li>
<li>Scale globally in minutes.</li>
<li>Deliver faster and e.t.c.</li>
</ul>
<h3 id="heading-why-template-your-infrastructure">Why template your infrastructure?</h3>
<p>With the advent of cloud computing, came several tools that enabled us to deploy the underlying infrastructure components that provide security and services to your servers by writing scripts.</p>
<p>Infrastructure as code (IaC) is to make operations faster by eliminating manual processes of navigating through the AWS console to provision resources and thus eliminating the slack in the process. </p>
<p>A code-based approach makes it easier to get more done in less time, you could introduce the version control tools, you could push templates into repositories for collaborations, semantic versioning of templates, introduce checks and reviews on the templates to reduce errors.  Interesting right 😊!</p>
<h3 id="heading-lets-get-started">Let's get started</h3>
<p>In this tutorial, you’ll learn how to deploy infrastructure using CloudFormation script, AWS proprietary tool for Infrastructure as Code. it's used for managing, configuring, and deploying infrastructure.</p>
<p><strong>Prerequisites</strong></p>
<ul>
<li>Version Control: A popular recommendation is <a target="_blank" href="https://git-scm.com">Git</a></li>
<li>Code Editor for YAML and JSON: <a target="_blank" href="https://code.visualstudio.com">Vscode</a> is a great lightweight code editor, with a plethora of plugins to customize your coding experience.</li>
<li>Amazon Web Services account: visit <a target="_blank" href="aws.amazon.com">aws.amazon.com</a> and sign in.</li>
</ul>
<h4 id="heading-create-access-key-id">Create Access key ID</h4>
<ul>
<li>In the AWS console, Choose the region closest to your location.</li>
<li>Navigate to the IAM service, click on users, create a user with "programmatic access." This allows us to use code to interact with AWS, instead of relying on mouse clicking in the AWS console.</li>
<li>For IAM access, choose “administrator access.” This is just for the initial setup of your account. Afterward, you’ll want to limit access to only what you need.</li>
</ul>
<p><em>Remember not to save these in your code or to check into any repositories. </em></p>
<p>After saving and creating the user, you should have this page below in the user's section:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642977878596/e9CfXFZyk.png" alt="Screenshot 2022-01-21 at 00.36.41.png" /></p>
<h4 id="heading-next-configure-the-aws-cli">Next, configure the AWS CLI</h4>
<p>let's follow these steps;</p>
<ul>
<li>Download and install the <a target="_blank" href="https://aws.amazon.com/cli/">AWS CLI</a> tool. </li>
<li>In the terminal, type <code>aws --version</code>: this displays the package version, thus verifying that you have the AWS CLI tool installed.</li>
<li>To set up your AWS CLI, type <code>aws configure</code> in the terminal. Next, when prompted for the AWS credentials paste in your  "Access Key ID" and "Secret access key" accordingly.</li>
<li>Next, use a region closest to your location.</li>
</ul>
<p>One way to check if your "AWS CLI" is set up properly is to try a command. You can try listing your S3 buckets with <code>aws s3 ls</code>. This will be blank if you have no S3 buckets. However, if you have no error message, then you’ve verified that your user has API access to communicate with AWS.</p>
<h4 id="heading-next-write-your-cloudformation-script">Next, write your cloudformation script</h4>
<p>CloudFormation uses a declarative language like SQL and not an imperative language. It lets you specify what you want, without requiring you to specify how to get it.</p>
<p>CloudFormation abstracts the complexities of resource dependencies. So that you don’t have to specify which resource to start up before another. There are cases where you can specify that a resource depends on another resource, but ideally, you’ll let CloudFormation take care of dependencies.</p>
<p>Cloud formation scripts can be written in YAML and JSON file formats. but YAML is the industry preferred version that’s used for AWS.</p>
<p>In this example, you'll provision a VPC on AWS below. Each VPC is a fundamental layer of your infrastructure. It isolates your network environment on a public cloud. </p>
<pre><code><span class="hljs-comment">#testcfn.yml</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">&gt; 
    Nextwebb / localhost
    This template deploys a VPC.
</span><span class="hljs-attr">Resources:</span>
    <span class="hljs-attr">TestVPC:</span>
        <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::EC2::VPC</span>
        <span class="hljs-attr">Properties:</span>
            <span class="hljs-attr">CidrBlock:</span> <span class="hljs-number">10.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">/16</span>
            <span class="hljs-attr">EnableDnsHostnames:</span> <span class="hljs-literal">true</span>
</code></pre><p>The example above provisions a resource, three parts constitute the script:</p>
<ul>
<li><p><em>Name</em>: A unique name you want to give to the resource. In this case, we called it "TestVPC".</p>
</li>
<li><p><em>Type</em>: Specifies the actual hardware resource that you’re deploying. we called it "AWS VPC".</p>
</li>
<li><p><em>Properties</em>: Specifies the configuration options for your resource. You can think of these as all the drop-down menus and checkbox options that you would see in the AWS console if you were to request the resource manually. It's a good practice to consult the cloudformation docs for resource properties and know which are optional or defaults. </p>
</li>
</ul>
<p>Just like we used above, the "CidrBlock" property specifies a range of(65,536) IP addresses, and the "<a target="_blank" href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html#vpc-dns-support">EnableDnsHostnames</a>" property Indicates whether the instances launched in the VPC get DNS hostnames.</p>
<h4 id="heading-how-to-run-cloudformation">How to run CloudFormation</h4>
<ul>
<li>In this tutorial, we’ll assume your CloudFormation file name is called testcfn.yml, and you’re giving your stack the name "ourfirsttest".</li>
<li><p>In the terminal, to use your yml code to request the resources, type the following in the same directory as your yml file:
<code>aws cloudformation create-stack 
--stack-name ourfirsttest 
--region us-west-2 
--template-body testcfn.yml</code></p>
<p>  <em>You may also want to use <code>update-stack</code> when you want to update an existing stack instead of destroying your stack and creating a new one.</em></p>
</li>
</ul>
<p>Once the above command is run, we should an output response like this. 
Depending on the response format you chose it would be text or object containing a stackId.</p>
<p><em>A stack is a group of resources. These are the resources that you want to deploy, and that are specified in the YAML file.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642981516573/7bNVZ_JBv.png" alt="Screenshot 2022-01-24 at 00.44.35.png" /></p>
<p>Navigate to the AWS console and select the cloudformation service. You should see the name of the stack you chose and the status of the resources provisioned.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642982203309/2my5ko_SV.png" alt="Screenshot 2022-01-21 at 01.37.34.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642982210972/NkMuUR_lpz.png" alt="Screenshot 2022-01-21 at 01.38.08.png" /></p>
<p>Navigate to the AWS console, you should have two VPC's. One is the default VPC that comes with your aws account and the other should be the one you created. You can differentiate them by the IP range and their Ids.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642981865271/tVEVW4VbM.png" alt="Screenshot 2022-01-21 at 01.37.09.png" /></p>
<p>Yey! everything looks good. We successfully provisioned a VPC with cloudformation. 🚀😇</p>
<p>In this tutorial,  you walked through setting up your AWS API user and used a cloud formation script to write a simple infrastructure. The VPC is a fundamental layer of your infrastructure. It isolates your network environment on a public cloud and other AWS resources and services operate inside of the VPC networks to provide cloud services.</p>
<p>You don't need to memorize the code that you need for each resource. You can find sample code in the <a target="_blank" href="https://docs.aws.amazon.com/index.html">documentation for CloudFormation</a> for examples of how to write your CloudFormation scripts.</p>
<p>Thank you for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a>, and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<p>#Cloudcaptain 👨‍✈️</p>
]]></content:encoded></item><item><title><![CDATA[Simple queues system in Nodejs 🚀]]></title><description><![CDATA[Modern applications grow to require expensive processing, asynchronous operations, necessary tasks delays before completions, logging, and triggering processes from initiated application/user events. 
Why queue system?
A queue system would thus,  eff...]]></description><link>https://blog.nextwebb.tech/simple-queues-system-in-nodejs</link><guid isPermaLink="true">https://blog.nextwebb.tech/simple-queues-system-in-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[message queue]]></category><category><![CDATA[npm]]></category><category><![CDATA[Redis]]></category><category><![CDATA[jobs]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Sat, 06 Nov 2021 07:45:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1636152111377/8M9qW9qKd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications grow to require expensive processing, asynchronous operations, necessary tasks delays before completions, logging, and triggering processes from initiated application/user events. </p>
<h2 id="why-queue-system">Why queue system?</h2>
<p>A queue system would thus,  efficiently cater to the increasing asynchronous operations from an increase of actions/events. thus reducing errors, improving user experience, and processing actions in the background, and executing functions or triggering an application service when it's ready.</p>
<p>Some tasks that can be handled by a job queue:</p>
<ul>
<li>Sending system-authored push notifications</li>
<li>Sending system-authored emails</li>
<li>communication with third applications and webhooks </li>
<li>event-driven programming </li>
<li>logging events, e.t.c </li>
</ul>
<p>Necessary terminology in the context of queues;</p>
<p><strong>Job</strong> - This is a task that can get pushed into a queue system, to be processed at a later stage in the application flow. the job is the Object that contains the required data for the consumer to process.</p>
<p><strong>Consumer</strong> - 
This is the task to be carried out in the queue process. such as sending emails, push notifications e.t.c</p>
<p><strong>Producer</strong> - The producer would represent the instantiated queue constructor. Many Jobs can be added to the specified named queue object; for processing to be carried out. such as push notifications, notifications, emails. 
The options argument will have options such as delay (when to execute the job from the time of creation), attempts (number of retries when a job fails), rate-limiting of jobs e.t.c.</p>
<h2 id="lets-get-started-with-a-simple-queue-system-implementation-with-bullhttpswwwnpmjscompackagebulldocumentation">Let's get started with a simple queue system implementation with <a target="_blank" href="https://www.npmjs.com/package/bull#documentation"><strong>Bull</strong></a></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1636183504844/4BqKBUMPx.png" alt="1*wk16U_GrKwWacETRhu9ScA.png" /></p>
<p>Bull sells its queue system implementation as </p>
<blockquote>
<p>The fastest, most reliable, Redis-based queue for Node. 
Carefully written for rock-solid stability and atomicity.</p>
</blockquote>
<p>Bull is fully is compatible with typescript, well tested, queue system UI, and built on top of Redis cache with a lot of helpful abstraction methods and functionalities. </p>
<p>Of course, the community could use more abstracted systems to simplify solutions when using the plethora of tools with their surging complexities 😅. </p>
<h3 id="implementation">Implementation</h3>
<ul>
<li>Install <a target="_blank" href="https://redis.io/topics/quickstart">Redis</a> locally or set up a Redis docker container as below; let's add a <code>Dockerfile</code> and a <code>docker-compose.yml</code>:</li>
</ul>
<pre><code><span class="hljs-comment"># docker-compose.yaml</span>

<span class="hljs-comment"># pull a light-weight redis image from docker</span>
<span class="hljs-comment"># open a reverse port </span>
<span class="hljs-attr">services:</span>
 <span class="hljs-attr">simple_queue_redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:6.2-alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">simple_queue_redis</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">ALLOW_EMPTY_PASSWORD=yes</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">6379</span><span class="hljs-string">:6379</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./redis:/db</span>
</code></pre><pre><code> <span class="hljs-comment"># docker-compose.yaml</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">14</span>-alpine

<span class="hljs-comment"># Create app directory</span>
WORKDIR /app

<span class="hljs-comment"># Install dependencies</span>
RUN yarn install
RUN yarn

<span class="hljs-comment"># Copy source files from the host computer to the container</span>
COPY . .

<span class="hljs-comment"># Run the app</span>
CMD [<span class="hljs-string">"yarn"</span>, <span class="hljs-string">"start:dev"</span>]
</code></pre><ul>
<li>Install the Bull package from NPM using the following command.</li>
</ul>
<p><code>npm install bull --save</code></p>
<ul>
<li>Create queues and processes.
We assume that we're working in a type-safe environment, hence the use of typescript.
Create a new file named <code>notificationQueue.ts</code> and paste the following code.
Here we assume we're working on a notification job.</li>
</ul>
<p>The <code>NotificationQueue</code> instance, in this case, would represent the producer and the notification job gets added to the consumer, the <code>notificationProcess</code>.</p>
<pre><code>  <span class="hljs-comment">// ./queues/notificationQueue.ts</span>

<span class="hljs-keyword">import</span> Queue <span class="hljs-keyword">from</span> <span class="hljs-string">'bull'</span>;

<span class="hljs-keyword">import</span> { notificationProcess } <span class="hljs-keyword">from</span> <span class="hljs-string">'../processes'</span>;

<span class="hljs-comment">// producer</span>
<span class="hljs-keyword">const</span> NotificationQueue = <span class="hljs-keyword">new</span> Queue(<span class="hljs-string">'Notification'</span>, process.env.REDIS_URL);

<span class="hljs-comment">// consumer</span>
NotificationQueue.process(notificationProcess);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> addNewNotification = <span class="hljs-function">(<span class="hljs-params">data: {
title: <span class="hljs-built_in">string</span>,
body: <span class="hljs-built_in">string</span>,
url: <span class="hljs-built_in">string</span>,
}
</span>) =&gt;</span> {
  NotificationQueue.add(data);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NotificationQueue;
</code></pre><p>The following code block defines the implementation of the push notification job from the server.</p>
<p>We're working with <a target="_blank" href="https://pusher.com/docs/beams/getting-started/web/sdk-integration/">pusher beams</a> for sending push messages from the server. it's easy to use, has good documentation, and helps abstract the complexities of socketing a server to allow messaging between the server and client 😀. </p>
<p>Install the pusher Nodejs SDK from NPM using the following command.</p>
<p><code>npm i @pusher/push-notifications-server</code></p>
<p>We won't be focusing on how pusher works in this tutorial, the official <a target="_blank" href="https://github.com/pusher/push-notifications-node#readme">docs</a> is a good resource.</p>
<pre><code>  <span class="hljs-comment">// ./processes/notification.process.ts</span>

<span class="hljs-keyword">import</span> { Job } <span class="hljs-keyword">from</span> <span class="hljs-string">'bull'</span>;
<span class="hljs-keyword">import</span> PushNotifications <span class="hljs-keyword">from</span> <span class="hljs-string">'@pusher/push-notifications-server'</span>;

<span class="hljs-keyword">const</span> beamsClient = <span class="hljs-keyword">new</span> PushNotifications({
  instanceId: process.env.PUSHER_BEAMS_INSTANCE,
  secretKey: process.env.PUSHER_BEAMS_SECRET,
});

<span class="hljs-keyword">const</span> NotificationProcess = <span class="hljs-keyword">async</span> (job: Job) =&gt; {
  <span class="hljs-keyword">const</span> {
    title,
    body,
    url,

  } = job.data <span class="hljs-keyword">as</span> {
title: <span class="hljs-built_in">string</span>,
body: <span class="hljs-built_in">string</span>,
url: <span class="hljs-built_in">string</span>,
};

  <span class="hljs-keyword">await</span> beamsClient.publishToInterests([userId], {
    web: {
      notification: {
        title,
        body,
        deep_link: <span class="hljs-string">`<span class="hljs-subst">${process.env.BASE_URL}</span><span class="hljs-subst">${url}</span>`</span>,
      },
    },

  });
 <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> NotificationProcess;
</code></pre><p>Save the file and run it using node <code>./index.ts</code>. A notification will be sent to the subscribed client. (not covered in this tutorial).</p>
<pre><code><span class="hljs-comment">// ./index.ts</span>
<span class="hljs-keyword">import</span> { NotificationQueueHelpers, } <span class="hljs-keyword">from</span> <span class="hljs-string">'.../queues'</span>;

...

 NotificationQueueHelpers.addNewNotification({
    <span class="hljs-attr">title</span>: <span class="hljs-string">"simple queue system"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-string">"push notification sent..."</span>,
    <span class="hljs-attr">url</span>: <span class="hljs-string">`/notification/<span class="hljs-subst">${user.id}</span>`</span>,
  });
</code></pre><h3 id="wrap-up">Wrap-up</h3>
<p>This is a simple example of a queue system using Bull to create queues and schedule Jobs. As mentioned above bull offers a lot more options to manage the Redis caching system behind the scenes such as Event listeners, Rate limiters, Job types, delayed jobs, number of retries, using Cron expressions, and etc.</p>
<p>The <a target="_blank" href="https://optimalbits.github.io/bull/"><strong>official documentation</strong></a> is still a great resource worth exploring, do check it out.</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<p>Happy coding 💙!</p>
]]></content:encoded></item><item><title><![CDATA[Asynchronous programming in Node.js]]></title><description><![CDATA[Asynchronous I/O is a form of input/output processing that permits other processing to continue before the transmission has finished.
Asynchronous programming in JavaScript can only be achieved with functions being first-class citizens of the languag...]]></description><link>https://blog.nextwebb.tech/asynchronous-programming-in-nodejs</link><guid isPermaLink="true">https://blog.nextwebb.tech/asynchronous-programming-in-nodejs</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[asynchronous]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[ES6]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Tue, 19 Oct 2021 01:41:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634521233498/JBF9bjIZi.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Asynchronous I/O is a form of input/output processing that permits other processing to continue before the transmission has finished.</p>
<p>Asynchronous programming in JavaScript can only be achieved with functions being first-class citizens of the language. Thus functions can be passed around like any other variable to other functions. </p>
<blockquote>
<p>Functions that can take other functions as arguments are called higher-order functions.</p>
</blockquote>
<p>In Javascript, one efficient way of handling asynchronous or expensive operations is using promises. This is because in an asynchronous operation there is no guarantee of exactly when the operation will complete and the result it will return. 
A promise object thus helps us to cater for the intermediate state of the operation, whether it is successful(resolve), pending(ongoing), or fails(reject).</p>
<pre><code><span class="hljs-selector-tag">Promise</span><span class="hljs-selector-class">.resolve</span>() <span class="hljs-comment">//success</span>
<span class="hljs-selector-tag">Promise</span><span class="hljs-selector-class">.reject</span>() <span class="hljs-comment">// fail</span>
</code></pre><h2 id="why-promises">Why Promises?</h2>
<p>We established above that a Promise object represents the result of an asynchronous function. Promises can be used to avoid chaining of callbacks, referred to as the  <code>Callback hell</code>.</p>
<h2 id="lets-get-started-by-converting-node-styled-callback-to-promise">let's get started by converting Node-styled callback to promise.</h2>
<h3 id="assumptions-lets-set-the-tone-for-the-discussion">Assumptions, let's set the tone for the discussion 😃</h3>
<ul>
<li>You're familiar with javascript</li>
<li>And you have some experience with the Node environment; if this is all checked ✅, we're set to learn about promises 😇.</li>
</ul>
<p>It's easier to work with promises when working on async operations, but unfortunately, Nodejs has many of its APIs written with callbacks. (Promises were updates to the ECMAScript)</p>
<p>Here's an example using the file stream API. <code>fs.readFile</code></p>
<pre><code><span class="hljs-keyword">const</span> fs = <span class="hljs-keyword">require</span>(<span class="hljs-string">'fs'</span>) 

fs.readFile(filePath, options, callback)
</code></pre><p>Here, the callback function contains at least two arguments. The first argument must be an error object and the data returned.</p>
<pre><code>fs.readFile(<span class="hljs-string">'file-path'</span>, <span class="hljs-function"><span class="hljs-params">(err, data)</span> =&gt;</span> {
  <span class="hljs-keyword">if</span> (err) {
     //<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> error
  } 
    //<span class="hljs-keyword">do</span> something <span class="hljs-keyword">with</span> data <span class="hljs-keyword">return</span>

})
</code></pre><p>Below we convert a callback styled Node API to a promise, using the <code>util.promisify</code> API</p>
<pre><code>const util = <span class="hljs-built_in">require</span>(<span class="hljs-string">'util'</span>);
const fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>);

const readFilePromise = util.promisify(fs.readFile)
readFilePromise(<span class="hljs-string">'file-path'</span>).<span class="hljs-keyword">then</span>(<span class="hljs-function"><span class="hljs-params">(data)</span> =&gt;</span> {
    //<span class="hljs-keyword">do</span> something <span class="hljs-keyword">with</span> data <span class="hljs-keyword">return</span>
}).<span class="hljs-keyword">catch</span>(<span class="hljs-function"><span class="hljs-params">(error)</span> =&gt;</span> {
    //<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> error
});
</code></pre><p>We can also use the <code>async/await</code> operation to resolve a promise. </p>
<pre><code><span class="hljs-keyword">const</span> readFilePromise = util.promisify(fs.readFile)

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> readFilePromise;
  <span class="hljs-built_in">console</span>.log(data);
}
</code></pre><h2 id="lets-create-promises">Let's Create Promises</h2>
<p>In javascript, we can create asynchronous functions that return a promise 😊 . see the example below.</p>
<pre><code>   <span class="hljs-keyword">const</span> myPromise = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>((resolve, reject) = {
     <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> 
       {
             <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'resolving the promise ...'</span>);
             resolve(<span class="hljs-string">'resolved!'</span>);
       }, <span class="hljs-number">5000</span>); <span class="hljs-comment">// usually would be an asychronous operation</span>

    reject(<span class="hljs-string">'rejected!'</span>);
});

<span class="hljs-built_in">console</span>.log(myPromise);
</code></pre><p>We can chain the Promises using <code>.then()</code> and <code>.catch()</code> higher-order functions. 
The <code>.then()</code> accepts a callback as an argument and invokes the resolved Promise. The <code>.catch()</code> accepts a callback and is executed when the Promise is rejected.</p>
<p>see the example below.</p>
<pre><code>const resolvedCallback = <span class="hljs-function"><span class="hljs-params">(data)</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data);
const rejectedCallback = <span class="hljs-function"><span class="hljs-params">(err)</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(err);

myPromise.<span class="hljs-keyword">then</span>(resolvedCallback).<span class="hljs-keyword">catch</span>(rejectedCallback);
</code></pre><h2 id="more-examples-handling-multiple-independent-promises">More examples handling Multiple independent promises</h2>
<p>Unlike the examples we wrote above, we can return the state of a promise object without conditions. this might be useful when we need to <code>resolve</code> or <code>reject</code> multiple independent asynchronous operations. it's as simple as writing;</p>
<pre><code><span class="hljs-selector-tag">Promise</span><span class="hljs-selector-class">.resolve</span>();
<span class="hljs-selector-tag">Promise</span><span class="hljs-selector-class">.reject</span>();
</code></pre><p>We notice in the Node environment, the Promise object is globally available, interesting right! 😎</p>
<p>When dealing multiple independent promises, the methods <code>Promise.all()</code> and <code>Promise.race()</code> can be useful. Both methods accept an array of promises as an argument.</p>
<p>While using <code>Promise. all()</code>, the <code>.then</code> method is only invoked after the last promise is <code>resolved</code>.</p>
<pre><code><span class="hljs-keyword">const</span> promiseArr = [
  <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">"data1"</span>)
  <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">"data2"</span>)
  <span class="hljs-built_in">Promise</span>.resolve(<span class="hljs-string">"data3"</span>)
]
</code></pre><pre><code><span class="hljs-built_in">Promise</span>.all(promiseArr).then(<span class="hljs-function"><span class="hljs-params">results</span> =&gt;</span>
  <span class="hljs-built_in">console</span>.log(results)
)

<span class="hljs-comment">// [ data1, data2, data3 ]</span>
</code></pre><p>While using <code>Promise.race()</code>, the <code>.then</code> method is invoked only when the first promise is <code>resolved</code>.</p>
<pre><code><span class="hljs-built_in">Promise</span>.race(promiseArr).then(<span class="hljs-function"><span class="hljs-params">results</span> =&gt;</span>
  <span class="hljs-built_in">console</span>.log(results)
)

<span class="hljs-comment">// data1</span>
</code></pre><p>One thing to note as well is that the least expensive function will always resolve first. 
 This enables us to run multiple asynchronous operations and act upon the one that finishes first.</p>
<h3 id="wrap-up">Wrap-up ✍️</h3>
<ul>
<li>A promise represents the state of execution of a function</li>
<li>A promise has 3 states; resolve, reject and pending. </li>
<li>Using async and await is a sugar coating wrapped around promises and it makes working with promises easy and readable</li>
<li>Await ensures it resolves the promise being executed and encourages return error early.</li>
<li>Promises and async/await accomplish the same thing. <ul>
<li>They make retrieving and handling expensive functions/asynchronous data easier.</li>
<li>They eliminate the need for callback hell/nested functions</li>
<li>Simplify error handling.</li>
<li>Reduce extraneous code</li>
<li>Make waiting for multiple concurrent calls to return and adding additional code in between calls easier.</li>
</ul>
</li>
</ul>
<p>Thanks for the audience and I hope you found this article helpful getting started with promises in Javascript 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<p>  <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Find out more about Graceful asynchronous programming with Promises</a> </p>
]]></content:encoded></item><item><title><![CDATA[Getting started with Prisma ORM for Nodejs]]></title><description><![CDATA[Many things in the tech sound intimidating when you first learn of them than they actually are. ORMs (Object Relational Mappers) are just one of them; we also have quite a number of options for any Nodejs application but I'll share some incites into ...]]></description><link>https://blog.nextwebb.tech/getting-started-with-prisma-orm-for-nodejs</link><guid isPermaLink="true">https://blog.nextwebb.tech/getting-started-with-prisma-orm-for-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Databases]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Mon, 02 Aug 2021 20:01:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1627934129824/LHJZwBN4jX.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many things in the tech sound intimidating when you first learn of them than they actually are. ORMs (Object Relational Mappers) are just one of them; we also have quite a number of options for any Nodejs application but I'll share some incites into getting started with Prisma ORM.</p>
<h2 id="what-are-orms">What are ORMs?</h2>
<p>Object-relational mapping conceptualizes the idea of being able to write queries like the one below, as well as much more complicated ones, using the object-oriented paradigm of your preferred programming language.
Essentially, ORMs help abstract the complexities of writing custom SQL queries to a database. </p>
<pre><code><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">user</span> <span class="hljs-keyword">where</span> countryId = <span class="hljs-number">2</span>
</code></pre><p>Well, just like any other tool or library, we have pros and cons to using them. For the most part, I stumbled on  <a target="_blank" href="prisma.io">Prisma</a>, and it's arguably an excellent tool for low to medium-level complexity and enterprise apps. </p>
<ul>
<li>You easily scaffold a Nodejs server with a database quickly with the least amount of code.</li>
<li>It's open-source,  it's compatible with a number of SQL such as PostgreSQL, MySQL, and SQLite.  </li>
<li>Out of the box support for typescript.</li>
<li>Many tools aren't properly documented and we have to rely on the community for help when stuck, but  <a target="_blank" href="https://www.prisma.io/docs/">Prisma</a>  documentation is quite delightful and very detailed. </li>
</ul>
<p>Next up we will look at some prerequisites for this tutorial, featuring Prisma, Express.js + typescript, and PostgreSQL.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>This tutorial assumes you've had some experience with Nodejs ;</p>
<ul>
<li>You have the Nodejs LTS installed</li>
<li>Scaffold an Expressjs app </li>
<li>Basic knowledge of Javascript/typescript </li>
<li>Some idea of how ORMs interact with databases </li>
</ul>
<h2 id="implementation">Implementation</h2>
<h3 id="lets-set-up-the-expressjs-app">Lets set Up the Express.js app</h3>
<ul>
<li>We’ll begin by setting up the Express.js app for a  Movies listing   API demo project. 
Navigate into a new folder First, then run the following to create a new project:</li>
</ul>
<p>we also assume you're using a Unix shell/terminal</p>
<pre><code>mkdir movies-listing-api
cd movies-listing-api
npm <span class="hljs-keyword">init</span> -y
</code></pre><ul>
<li>Next, install Express.js, our minimalistic framework for the Star Wars REST API:</li>
</ul>
<pre><code>npm <span class="hljs-keyword">install</span> <span class="hljs-comment">--save express</span>
</code></pre><ul>
<li>Next, setup typescript dev dependencies</li>
</ul>
<pre><code>npm <span class="hljs-keyword">install</span> typescript <span class="hljs-comment">--save-dev</span>
npm <span class="hljs-keyword">install</span> @types/node <span class="hljs-comment">--save-dev</span>
</code></pre><ul>
<li>Next, Create a tsconfig.json file
The tsconfig.json is where we define the TypeScript compiler options. We can create a tsconfig with several options sets. it should be at the root directory of the project.</li>
</ul>
<pre><code><span class="hljs-attribute">npx</span> tsc --init --rootDir src --outDir build \
--esModuleInterop --resolveJsonModule --lib es6 \
--module commonjs --allowJs <span class="hljs-literal">true</span> --<span class="hljs-literal">no</span>ImplicitAny <span class="hljs-literal">true</span>
</code></pre><p>At this point, you should have a tsconfig.json that looks like this:</p>
<pre><code>{
  "compilerOptions": {
    <span class="hljs-comment">/* Basic Options */</span>
    // "incremental": <span class="hljs-keyword">true</span>,                   <span class="hljs-comment">/* Enable incremental compilation */</span>
    "target": "es5",                          <span class="hljs-comment">/* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */</span>
    "module": "commonjs",                     <span class="hljs-comment">/* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */</span>
    "lib": ["es6"],                     <span class="hljs-comment">/* Specify library files to be included in the compilation. */</span>
    "allowJs": <span class="hljs-keyword">true</span>,                          <span class="hljs-comment">/* Allow javascript files to be compiled. */</span>
    // "checkJs": <span class="hljs-keyword">true</span>,                       <span class="hljs-comment">/* Report errors in .js files. */</span>
    // "jsx": "preserve",                     <span class="hljs-comment">/* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */</span>
    // "declaration": <span class="hljs-keyword">true</span>,                   <span class="hljs-comment">/* Generates corresponding '.d.ts' file. */</span>
    // "declarationMap": <span class="hljs-keyword">true</span>,                <span class="hljs-comment">/* Generates a sourcemap for each corresponding '.d.ts' file. */</span>
    // "sourceMap": <span class="hljs-keyword">true</span>,                     <span class="hljs-comment">/* Generates corresponding '.map' file. */</span>
    // "outFile": "./",                       <span class="hljs-comment">/* Concatenate and emit output to single file. */</span>
    "outDir": "build",                          <span class="hljs-comment">/* Redirect output structure to the directory. */</span>
    "rootDir": "src",                         <span class="hljs-comment">/* Specify the root directory of input files. Use to control the output directory structure with --outDir. */</span>
    // "composite": <span class="hljs-keyword">true</span>,                     <span class="hljs-comment">/* Enable project compilation */</span>
    // "tsBuildInfoFile": "./",               <span class="hljs-comment">/* Specify file to store incremental compilation information */</span>
    // "removeComments": <span class="hljs-keyword">true</span>,                <span class="hljs-comment">/* Do not emit comments to output. */</span>
    // "noEmit": <span class="hljs-keyword">true</span>,                        <span class="hljs-comment">/* Do not emit outputs. */</span>
    // "importHelpers": <span class="hljs-keyword">true</span>,                 <span class="hljs-comment">/* Import emit helpers from 'tslib'. */</span>
    // "downlevelIteration": <span class="hljs-keyword">true</span>,            <span class="hljs-comment">/* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */</span>
    // "isolatedModules": <span class="hljs-keyword">true</span>,               <span class="hljs-comment">/* Transpile each file as a separate module (similar to 'ts.transpileModule'). */</span>

    <span class="hljs-comment">/* Strict Type-Checking Options */</span>
    "strict": <span class="hljs-keyword">true</span>,                           <span class="hljs-comment">/* Enable all strict type-checking options. */</span>
    "noImplicitAny": <span class="hljs-keyword">true</span>,                    <span class="hljs-comment">/* Raise error on expressions and declarations with an implied 'any' type. */</span>
    // "strictNullChecks": <span class="hljs-keyword">true</span>,              <span class="hljs-comment">/* Enable strict null checks. */</span>
    // "strictFunctionTypes": <span class="hljs-keyword">true</span>,           <span class="hljs-comment">/* Enable strict checking of function types. */</span>
    // "strictBindCallApply": <span class="hljs-keyword">true</span>,           <span class="hljs-comment">/* Enable strict 'bind', 'call', and 'apply' methods on functions. */</span>
    // "strictPropertyInitialization": <span class="hljs-keyword">true</span>,  <span class="hljs-comment">/* Enable strict checking of property initialization in classes. */</span>
    // "noImplicitThis": <span class="hljs-keyword">true</span>,                <span class="hljs-comment">/* Raise error on 'this' expressions with an implied 'any' type. */</span>
    // "alwaysStrict": <span class="hljs-keyword">true</span>,                  <span class="hljs-comment">/* Parse in strict mode and emit "use strict" for each source file. */</span>

    <span class="hljs-comment">/* Additional Checks */</span>
    // "noUnusedLocals": <span class="hljs-keyword">true</span>,                <span class="hljs-comment">/* Report errors on unused locals. */</span>
    // "noUnusedParameters": <span class="hljs-keyword">true</span>,            <span class="hljs-comment">/* Report errors on unused parameters. */</span>
    // "noImplicitReturns": <span class="hljs-keyword">true</span>,             <span class="hljs-comment">/* Report error when not all code paths in function return a value. */</span>
    // "noFallthroughCasesInSwitch": <span class="hljs-keyword">true</span>,    <span class="hljs-comment">/* Report errors for fallthrough cases in switch statement. */</span>

    <span class="hljs-comment">/* Module Resolution Options */</span>
    // "moduleResolution": "node",            <span class="hljs-comment">/* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */</span>
    // "baseUrl": "./",                       <span class="hljs-comment">/* Base directory to resolve non-absolute module names. */</span>
    // "paths": {},                           <span class="hljs-comment">/* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */</span>
    // "rootDirs": [],                        <span class="hljs-comment">/* List of root folders whose combined content represents the structure of the project at runtime. */</span>
    // "typeRoots": [],                       <span class="hljs-comment">/* List of folders to include type definitions from. */</span>
    // "types": [],                           <span class="hljs-comment">/* Type declaration files to be included in compilation. */</span>
    // "allowSyntheticDefaultImports": <span class="hljs-keyword">true</span>,  <span class="hljs-comment">/* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */</span>
    "esModuleInterop": <span class="hljs-keyword">true</span>,                  <span class="hljs-comment">/* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */</span>
    // "preserveSymlinks": <span class="hljs-keyword">true</span>,              <span class="hljs-comment">/* Do not resolve the real path of symlinks. */</span>
    // "allowUmdGlobalAccess": <span class="hljs-keyword">true</span>,          <span class="hljs-comment">/* Allow accessing UMD globals from modules. */</span>

    <span class="hljs-comment">/* Source Map Options */</span>
    // "sourceRoot": "",                      <span class="hljs-comment">/* Specify the location where debugger should locate TypeScript files instead of source locations. */</span>
    // "mapRoot": "",                         <span class="hljs-comment">/* Specify the location where debugger should locate map files instead of generated locations. */</span>
    // "inlineSourceMap": <span class="hljs-keyword">true</span>,               <span class="hljs-comment">/* Emit a single file with source maps instead of having a separate file. */</span>
    // "inlineSources": <span class="hljs-keyword">true</span>,                 <span class="hljs-comment">/* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */</span>

    <span class="hljs-comment">/* Experimental Options */</span>
    // "experimentalDecorators": <span class="hljs-keyword">true</span>,        <span class="hljs-comment">/* Enables experimental support for ES7 decorators. */</span>
    // "emitDecoratorMetadata": <span class="hljs-keyword">true</span>,         <span class="hljs-comment">/* Enables experimental support for emitting type metadata for decorators. */</span>

    <span class="hljs-comment">/* Advanced Options */</span>
    "resolveJsonModule": <span class="hljs-keyword">true</span>                 <span class="hljs-comment">/* Include modules imported with '.json' extension */</span>
  }
}
</code></pre><ul>
<li>Next, set up the entry point of the application by checking out into the project/src directory and create two files app.ts and server.ts .</li>
</ul>
<pre><code><span class="hljs-selector-tag">mkdir</span> &amp;&amp;  <span class="hljs-selector-tag">cd</span> <span class="hljs-selector-tag">src</span>
<span class="hljs-selector-tag">touch</span> <span class="hljs-selector-tag">server</span><span class="hljs-selector-class">.ts</span>
<span class="hljs-selector-tag">touch</span> <span class="hljs-selector-tag">app</span><span class="hljs-selector-class">.ts</span>
</code></pre><p>the files should look like this :
This will contain the express routing and handle API requests.</p>
<pre><code>#app.ts
<span class="hljs-keyword">import</span> express, { Application } <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;

<span class="hljs-comment">// express app</span>
<span class="hljs-keyword">const</span> app: Application = express();


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> app;
</code></pre><p>This Nodejs server, serves the express application and exposes it over  process.env.PORT or 8000;</p>
<pre><code>#server.ts
<span class="hljs-keyword">import</span> app <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span>;

<span class="hljs-comment">// constants</span>
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">8000</span>;

<span class="hljs-comment">// listen</span>
app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`⚡ on http://localhost:<span class="hljs-subst">${PORT}</span>/api`</span>));
</code></pre><ul>
<li>Next up, we’ll set up a PostgreSQL database to create our tables for the star-wars movie API.</li>
</ul>
<h3 id="set-up-a-local-postgresql-database-with-docker">Set Up a Local PostgreSQL Database with docker</h3>
<p>Create a Postgres image at the root of the project directory, build the image, and run it.</p>
<pre><code><span class="hljs-selector-tag">touch</span> <span class="hljs-selector-tag">postgres</span><span class="hljs-selector-class">.yaml</span>
</code></pre><p>the docker-compose file should look this: </p>
<pre><code><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>

  <span class="hljs-attr">postgres:</span>
    <span class="hljs-comment"># For more details on configuring the Postgres Docker image, see:</span>
    <span class="hljs-comment">#   https://hub.docker.com/_/postgres/</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:10.3-alpine</span> <span class="hljs-comment">#light weight </span>

    <span class="hljs-comment"># Expose the default Postgres port on localhost</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'5432:5432'</span>
      <span class="hljs-comment"># allow connections from the host machine</span>
      <span class="hljs-comment"># an instance of the postgres image = postgres_docker(container)</span>
    <span class="hljs-attr">network_mode:</span> <span class="hljs-string">bridge</span> <span class="hljs-comment"># port available on localhost </span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">postgres_docker</span>

    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">'postgres'</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">'pgpass'</span>
      <span class="hljs-attr">POSTGRES_DB:</span> <span class="hljs-string">'postgres'</span>

    <span class="hljs-comment"># Copy files from dbinit into the image so that they will be run on boot</span>
    <span class="hljs-comment"># mount db</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./initdb:/docker-entrypoint-initdb.d</span>
</code></pre><p>If you want to run a PostgreSQL database with Docker, use the following command:</p>
<pre><code><span class="hljs-selector-tag">docker</span> <span class="hljs-selector-tag">compose</span> <span class="hljs-selector-tag">-f</span> <span class="hljs-selector-tag">postgres</span><span class="hljs-selector-class">.yaml</span> <span class="hljs-selector-tag">up</span>
</code></pre><p>The docker run command creates a new docker container named postgres_docker, exposing the container port 5432 to the local port 5432. </p>
<p>Our database is up and running. Next up, we will add Prisma to our Node.js project and create our schema.</p>
<h3 id="install-prisma-orm-to-your-nodejs-project">Install Prisma ORM to Your Node.js Project</h3>
<p>Run the following command, which installs Prisma as a dev dependency</p>
<pre><code>npm <span class="hljs-keyword">install</span> prisma <span class="hljs-comment">--save-dev</span>
</code></pre><p>After that, initialize the Prisma schema with the following command:</p>
<pre><code>npx prisma <span class="hljs-keyword">init</span>
</code></pre><p>Next, add the database connection string to the env file,
it should look this:</p>
<pre><code><span class="hljs-comment">#.env</span>
<span class="hljs-attr">DATABASE_URL</span>=<span class="hljs-string">"postgresql://postgres:pgpass@localhost:5432/movies_storage?schema=public"</span>
</code></pre><h3 id="add-models-and-run-prisma-migration">Add Models and Run Prisma Migration</h3>
<p>the Prisma folder at the root directory contains prisma.schema file, in it we define our database tables for Movies and Users with their relation.</p>
<pre><code>datasource db {
  provider = <span class="hljs-string">"postgresql"</span>
  url      = env(<span class="hljs-string">"DATABASE_URL"</span>)
}

generator client {
  provider = <span class="hljs-string">"prisma-client-js"</span>
}

model Movie {
  id        Int      <span class="hljs-meta">@id</span> <span class="hljs-meta">@default(autoincrement())</span>
  title   String <span class="hljs-meta">@unique</span>
  description     String

  createdAt DateTime <span class="hljs-meta">@default(now())</span>
}
</code></pre><p>We have added One table to the schema. The Movies table, which has an auto-increment ID, the title field that must be unique, description, and createdAt field that defaults to the current date.</p>
<p>To convert these models into PostgreSQL database tables, run the following command:</p>
<pre><code>prisma migrate dev <span class="hljs-comment">--name init --preview-feature</span>
</code></pre><p>This generates the migration file, that contains the SQL that creates the tables in the PostgreSQL database.</p>
<h3 id="api-to-interact-with-movie-listings">API to interact with movie listings</h3>
<p>We will Update the app.ts file we created in a previous step and add the POST Movies and GET Movies API.</p>
<pre><code><span class="hljs-keyword">const</span> app: Application = express();

<span class="hljs-keyword">import</span> { PrismaClient } from <span class="hljs-string">'@prisma/client'</span>;
<span class="hljs-keyword">const</span> prisma = <span class="hljs-keyword">new</span> PrismaClient();

app.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/'</span>, (req, res) =&gt; {
  res.json({message: <span class="hljs-string">'alive'</span>});
});

app.post(<span class="hljs-string">'/movies'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> {title, description} from <span class="hljs-string">'req.body'</span>;
<span class="hljs-keyword">await</span> prisma.movie.create({
     data: {
     title,
     description,
}
    });
   res.json({
    message: <span class="hljs-string">"movie created"</span>
  });
});

app.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/movies:id'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
<span class="hljs-keyword">const</span> movie =  <span class="hljs-keyword">await</span> prisma.movie.findUnique({
   where:{
   id: req.params.id
},
  });

  res.json({
    data: movie,
  });
}

app.<span class="hljs-keyword">get</span>(<span class="hljs-string">'/movies'</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> allMovies =  <span class="hljs-keyword">await</span> prisma.movie.findMany({});

  res.json({
    data: allMovies,
  });
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> app;
</code></pre><h3 id="wrap-up">Wrap-up</h3>
<p>We built a Movie listing API with Express.js+typescript and Prisma ORM, using a PostgreSQL database running on Docker. I know we barely scratched the surface of what Prisma offers, nonetheless, I'm hoping this gives you some insights into adding Prisma ORM to your Nodejs apps.</p>
<p>To finish up, I suggest you further explore some great Prisma ORM features. For instance, query optimization, database transactions, and aggregate queries are good reads.</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Things I Wish I knew When I Started Coding]]></title><description><![CDATA[Hi, I'm Nextwebb 👋... I wrote my first line of code 4years ago and since then it's totally changed my life.
For me, coding brings some sense of satisfaction. The skill of being able to program computers to do what you want, learning how applications...]]></description><link>https://blog.nextwebb.tech/things-i-wish-i-knew-when-i-started-coding</link><guid isPermaLink="true">https://blog.nextwebb.tech/things-i-wish-i-knew-when-i-started-coding</guid><category><![CDATA[#codenewbies]]></category><category><![CDATA[software development]]></category><category><![CDATA[Programming Tips]]></category><category><![CDATA[Career]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Fri, 30 Apr 2021 01:04:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619742975129/-a1rqiuCa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi, I'm Nextwebb 👋... I wrote my first line of code 4years ago and since then it's totally changed my life.</p>
<p>For me, coding brings some sense of satisfaction. The skill of being able to program computers to do what you want, learning how applications work under the hood and I loved video games... yea for some techies,  that was the spark.. it was exciting! I had a useful skill!  🤩. And The curiosity has kept me relentlessly getting better 😃.</p>
<p>Asides from freelancing and teaching coding, I had my first coding job at a hospitality firm in 2019 and since then I've had the resolve, " Coding every day can be fun until it isn't 😂! ". </p>
<p>During these years I have faced the good, the bad, and the ugly side of a programming job 😅.</p>
<p>I'll talk about tips I wish I knew when I started out learning tech 🖊️.</p>
<h2 id="programming-is-not-about-coding-programming-is-about-solving-problems-with-coding">Programming is not about coding, programming is about solving problems with coding.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619621067598/AdFkgisFY.png" alt="1*6CpHbDzAAxtuw5dNcDeM9A.png" />
In my opinion, this mindset can change a lot about your perspective and accelerate your growth.</p>
<p>Being able to see programming not as a hobbyist but as a professional who solves problems with technology. if we adopt this mentality the developer community will be less toxic...leave out emotions and identify opportunities as a software developer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619625004011/Qf8onHjx5.jpeg" alt="windows-kRWY72TKB0Y-unsplash.jpg" /></p>
<p>The truth is, software development has a lot of potentials, not only as a career but as a tool for entrepreneurship. As a developer, you have the ability to create anything that comes to mind with only the help of a computer. The skill to code anything by learning programming languages. The possibility to turn ideas into reality.</p>
<h2 id="communication-skills-are-just-as-important-as-coding-skills">Communication Skills Are Just As Important As Coding Skills.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619626050659/Tf3L02-u2.jpeg" alt="headway-5QgIuuBxKwM-unsplash.jpg" /></p>
<p>When we start out learning to code we invest a lot in our growth. for me, I used up all my free time coding and coding and more coding 😆!</p>
<p>Well while coding is a great skill to have... it's not enough 🤔!</p>
<p>Being able to sell yourself as a good developer is as important as being a good developer. Don’t be afraid to speak your mind and don’t wait for someone to offer you a senior / tech-lead position. Be this person, first 😉!</p>
<p>In the tech space it's important you sell yourself as a professional and not a hobbyist building and maintaining an online presence. Twitter, networking events, writing/reading articles and blogs,  and communicating effectively are places to start.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619627376567/zH4k63HFs.jpeg" alt="hack4justice20.jpeg" />
<em>hack4justice2020 hackathon</em></p>
<p>Tech is communal. that's why we have teams, pairing, open-source, stack overflow, conferences, books, hackathons, and blogs. Take advantage of this! 🚀</p>
<h2 id="be-open-to-change-and-willing-to-learn">Be Open to change and willing to learn.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619628044345/YFt9hcBHO.jpeg" alt="avel-chuklanov-DUmFLtMeAbQ-unsplash.jpg" /></p>
<p>Software engineering has changed a lot 🤔.
No one reinvents the wheel  (waste of time). Time is a scarce commodity to software engineers 😏
Professionals have been reduced to gluing technologies together, from third-party libraries, APIs, frameworks, and many times Open-source software, with the aim of producing and innovating a product(SAAS).</p>
<p>Staying relevant is a constant need for all professionals, but perhaps even more so for Developers. With technology changing at such a fast clip, it will be survival of the fastest in many parts of the industry.</p>
<ul>
<li><p>Stay current with industry news/ tech stack and OTHER news</p>
</li>
<li><p>I know we love to build. We love to code 😅...Just Use modern libraries. They'll generally make your life easier. And At least companies use them 🤷‍♂ </p>
</li>
<li><p>Don't make the big mistake to stop your education. You’ve worked hard to get where you are, and much of that doubtlessly included education (whether formal or informal). </p>
</li>
<li><p>The best way to learn is to connect with your peers; online communities are a great place to do this. Twitter has a great dev community. Take advantage of it and learn. </p>
</li>
<li><p>Attend meetups and tech conferences</p>
</li>
<li><p>Simply reading new job descriptions and staying connected to the market will tell you a world of information about what practical skills are generating the most interest and how to level up.</p>
<p>Well, take advantage of the changing modern sphere of software.</p>
</li>
</ul>
<h2 id="you-will-have-imposter-syndrome">You will have imposter syndrome.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619734689170/XwPnTIkwb.jpeg" alt="tim-gouw-1K9T5YiZ2WU-unsplash.jpg" />
Moments you feel like a fraud running out of luck and soon to be figured out 😩.
It's normal and it happens to everyone and at whatever level you are. When we feel this way it's important we try not to make it deter us or we dwell so much on how much we suck at programming 😏.</p>
<p><strong>How to counter our internal critics</strong></p>
<ul>
<li>Accept and move forward: You won't know everything. it's okay to know a few things.</li>
<li>Share your knowledge</li>
<li>Try to itemize your accomplishments</li>
<li>Give kudos to awesome people and help people</li>
<li>Teach what you know</li>
<li>What gives you credibility?<ul>
<li>knowledge</li>
<li>Experience</li>
<li>Contributions</li>
</ul>
</li>
<li><p>Write a bio, what to include:</p>
<ul>
<li>Roles: e.g team lead, project manager, volunteer, backend developer, frontend developer, community lead, mentor.</li>
<li>Skills: e.g show empathy, resolve conflicts, team player, communicate effectively.</li>
<li>Strengths: I'm relentless, I strive to be better. </li>
<li>Experiences: I've contributed value to employers, I have skills I'm proud of.</li>
<li>Values: positive attitude, communication, open-mindedness</li>
</ul>
<p>The tips outlined above are worth trying out,  For a couple of reasons see it as a good thing, you have nothing to lose 🤷‍♂️. 
it could help you keep learning and growing. it could serve as a moral compass helping you keep the right balance between humility and confidence 😀. </p>
</li>
</ul>
<h2 id="take-care-of-your-physical-and-mental-health">Take care of your physical and mental health.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619734781679/lQOxJECaz.jpeg" alt="olia-nayda-3TQ8I-sR9a8-unsplash.jpg" /></p>
<p>There's a thing as being addicted to programming.  Everything has a cost. Let's always keep the cost In check.</p>
<p>find a balance between work and life. in the long run, we're not robots. we can get overwhelmed by work, get tired, lose motivation, relationships can suffer and we can burn out. </p>
<p>In the long run, It's not a healthy habit that we work so much that we skip meals, rest, and some leisure time we don't stress overwork. Programming can be both mentally and physically exhausting. it's important we strive to rest well, we'll be surprised by how much proper night rest, can lead to a productivity boost at work 😃. </p>
<h2 id="you-wont-know-everything">You won't know everything.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619737896869/9WZRQgB2K.png" alt="image.png" />
If it's one thing I've learned as a programmer: it's to approach programming languages as just tools to get things done. The things that matter most are on a level above language syntax or libraries.</p>
<p>Don't get caught up in needless debates on which is best. As a matter of fact, the best skill a developer can have is being a problem solver.</p>
<p>You're always going to be at a point where you're going to need to know more and that's always going to be the case, especially in this industry. so you're never going to have all your docs in a row, you're gonna need to study more.  so just start building something.
anyone who's willing to hire you just wants to see someone that's willing to learn wants to pick up new things and gets the job done.</p>
<h2 id="take-your-time-and-learn-the-fundamentals-well">Take your time and learn the fundamentals well.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619737312210/LuqkK9pp1.png" alt="39dca7ad05cb2940b4d9a8bfe4a408da.png" /></p>
<p>It's okay to have big goals and plans. But it could sometimes be a snare to rush and skip the essential process of learning. Knowing and tracking what you know is important. </p>
<p>My advice to a budding developer would be to pick one language and learn it well. don't be swayed by the hype in the community, many times it's influenced by people's sentiments and not the market need. for example, JavaScript is not perfect but it works. so learn it well.</p>
<p>When it comes to software development, think long-term. Software Development is a marathon, not a sprint.</p>
<p>You can't learn 10 technologies in a week and build a full-scale app the second week 💔.</p>
<p>Take your time, and enjoy the journey. There is no rush. 😊</p>
<p>I am not where I want to be yet, and every day is a learning experience for me! 💪</p>
<p>Here we go! that concludes my top 7 tips article on this subject. I hope this helps a newbie do better and make better decisions. </p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
]]></content:encoded></item><item><title><![CDATA[Scaleable Authorization Management Made Easy With CASL.JS 😀]]></title><description><![CDATA[Introduction
CASL.JS at its core is an Isomorphic Authorization
JavaScript library ".  🤔

The fancy word “isomorphic” means that you can use the library on both the frontend and backend in exactly the same way.  - Sergii Stotskyi
CASL (pronounced /ˈ...]]></description><link>https://blog.nextwebb.tech/scaleable-authorization-management-made-easy-with-casljs</link><guid isPermaLink="true">https://blog.nextwebb.tech/scaleable-authorization-management-made-easy-with-casljs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[authorization]]></category><category><![CDATA[Security]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Mon, 01 Mar 2021 09:11:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1614583201675/sjh24-iqm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p><a target="_blank" href="https://casl.js.org/v5/en/">CASL.JS</a> at its core is an Isomorphic Authorization
JavaScript library ".  🤔</p>
<blockquote>
<p>The fancy word “isomorphic” means that you can use the library on both the frontend and backend in exactly the same way. <em> - </em>Sergii Stotskyi</p>
<p>CASL (pronounced /ˈkæsəl/, like a castle) is an isomorphic authorization JavaScript library that restricts what resources a given client is allowed to access. It's designed to be incrementally adoptable and can easily scale between a simple claim-based and fully featured subject and attribute-based authorization. It makes it easy to manage and share permissions across UI components, API services, and database queries.</p>
</blockquote>
<p>CASL is versatile, you can start with simple role-based access control and scale your apps to include fully-featured attribute-based access control.</p>
<p>CASL is declarative, it allows you to define permissions in the memory of the server-side using a domain-specific language that matches your business requirements almost word for word.</p>
<p>CASL is TypeSafe, it’s written in TypeScript, this makes apps safer and the developer experience more enjoyable.</p>
<p>CASL is small, it’s just ~4.5KB mingzipped and can be even smaller, thanks to tree-shaking! The minimum size is ~1.5KB.</p>
<h2 id="backstory">Backstory</h2>
<iframe src="https://giphy.com/embed/ljQg9SxQLlggDtrPoj" width="480" height="392" class="giphy-embed"></iframe>

<p>So while working on a Nodejs application at work; the team spent weeks researching and working on a hybrid access control framework that will be both scalable and dynamically change as the business needs require it.  </p>
<p>Recent developments have shown that attribute-based access control (ABAC) can provide flexible and fine-grained access control in both dynamic distributed systems and enterprise applications. As only the attributes of the subject, object, and environment are considered, most current solutions of typical Role based-access-control (RBAC) schemes cannot scale permissions to resources for a range of users with varied attributes.</p>
<p>Also in an (ABAC) framework,  the objective attributes are gained or defined by the resources themselves under certain conditions, such as time, location, IP address, and E.T.C. </p>
<p>So in the hybrid access control framework, permission to resources is modeled across roles, claims,  attributes of users on the platform; such as user-defined attributes specified by the profile owner, such as gender, name, job, hometown, hobbies, and etc.</p>
<p>Well building a custom hybrid access control framework,  as versatile as discussed above can be quite resource-heavy, especially for small teams 🙄. </p>
<p>We found <a target="_blank" href="https://github.com/stalniy/casl">CASL JS</a> authorization library to be awesome, easy to adopt, heavily maintained, and efficiently capable of scaling with the business for dynamically managing permission to resources 😃. </p>
<p><strong>ASSUMPTIONS</strong>. I assume you already have some knowledge of <a target="_blank" href="https://nodejs.org/en/">Nodejs</a> and managing permissions in your apps by claims or roles 😌. </p>
<h2 id="get-started-talk-is-cheap">Get started - TALK IS CHEAP 😆</h2>
<p>1). Install @casl/ability as a dependency in your Nodejs application: </p>
<p><code>npm i @casl/ability</code></p>
<p>2).  Define Abilities</p>
<p>There are 3 ways you can define abilities:</p>
<pre><code>- <span class="hljs-keyword">using</span> defineAbility <span class="hljs-keyword">function</span>
- <span class="hljs-keyword">using</span> AbilityBuilder <span class="hljs-keyword">class</span>
- <span class="hljs-keyword">using</span> <span class="hljs-type">JSON</span> objects
</code></pre><p>In this example, we'll be using the <code>defineAbility function</code></p>
<p>This function allows the creation of an Ability instance using can and cannot methods. It allows to define and use Ability instance without writing too much code.</p>
<pre><code><span class="hljs-comment">//ability/defineAbility.js</span>


<span class="hljs-keyword">const</span> { AbilityBuilder, Ability } = <span class="hljs-keyword">require</span>(<span class="hljs-string">"@casl/ability"</span>);

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@param</span> user contains details about logged in user: its id, name, email, etc
 */</span>
<span class="hljs-comment">// Define abilities for subjects here</span>

<span class="hljs-comment">// permissions on Organization</span>
exports.defineAbilitiesOnOrganizationFor = (user) =&gt; {
  <span class="hljs-keyword">const</span> { can, cannot, rules } = <span class="hljs-keyword">new</span> AbilityBuilder(Ability);

  <span class="hljs-comment">// condition == True</span>
  <span class="hljs-keyword">if</span> (user.isAdmin) {
    <span class="hljs-comment">// can manage (i.e., do anything) own Oganization</span>
    can(
      <span class="hljs-string">"manage"</span>, <span class="hljs-comment">// can do everything</span>
      <span class="hljs-string">"Oganization"</span>, <span class="hljs-comment">//  Organization collection</span>
      [<span class="hljs-string">"email"</span>,
        <span class="hljs-string">"phone"</span>,
        <span class="hljs-string">"password"</span>,
        <span class="hljs-string">"firstName"</span>, <span class="hljs-comment">// feilds that can be managed</span>
        <span class="hljs-string">"lastName"</span>
      ],
      { _id: user._id } <span class="hljs-comment">// condition , if OrgId=user.Id (belongs to an Org)</span>
    );

    <span class="hljs-comment">// But cannot delete Oganization</span>
    cannot(
      <span class="hljs-string">"delete"</span>, <span class="hljs-comment">// cannot delete Organization any Organization</span>
      <span class="hljs-string">"Organization"</span> <span class="hljs-comment">// collection Organization</span>
    );
  }

  <span class="hljs-keyword">if</span> (user.isSuperAdmin) {
    <span class="hljs-comment">// define the abilities for superAdmin on organization(subject)</span>
    can(
      <span class="hljs-string">"manage"</span>,
      <span class="hljs-string">"Organization"</span>
    );
  }

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Ability(rules);
};
</code></pre><p>in the above implementation we defined permissions for an "admin" and "super admin", we further set conditions for the organization as a resource that can be managed by both users.</p>
<p>CASL operates on the abilities level, that is what a user can actually do in the application. An ability itself depends on the 4 parameters (the last 3 are optional):</p>
<p> <strong>User Action</strong>
    Describes what users can actually do in the app. User action is a word (usually a verb) that depends on the business logic (e.g., prolong, read). 
Very often it will be a list of words from CRUD - create, read, update and delete.</p>
<p><strong>Subject</strong>
    The subject or subject type on which you want to check user action. Usually, this is a business (or domain) entity (e.g., Subscription, Article, User, Organization). 
The relation between subject and subject type is the same as the relation between an object instance and its class.</p>
<p> <strong>Fields</strong>
    Can be used to restrict user action only to matched subject's fields (e.g., to allow the admin to update a field of a resource and disallow to update certain fields)</p>
<p><strong> Conditions</strong>
    Criteria that restricts user action only to matched subjects. 
This is useful when you need to give permission on specific subjects (e.g., to allow the user to manage their own profile accounts)</p>
<p>YES;  easily business requirements can be translated into CASL's rules 🤩.</p>
<p>3).  Database integration
CASL has a complementary package [@casl/mongoose] which provides easy integration with MongoDB and [mongoose].</p>
<p>To install dependency: <code>npm install @casl/mongoose</code></p>
<pre><code><span class="hljs-comment">// Organization.js</span>


<span class="hljs-keyword">import</span> { AbilityBuilder } <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/ability'</span>;
<span class="hljs-keyword">import</span> { accessibleRecordsPlugin } <span class="hljs-keyword">from</span> <span class="hljs-string">'@casl/mongoose'</span>;
<span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>;
<span class="hljs-keyword">import</span> {defineAbilitiesOnOrganizationFor} <span class="hljs-keyword">from</span> <span class="hljs-string">'ability/defineAbility.js'</span>

mongoose.plugin(accessibleRecordsPlugin);

<span class="hljs-keyword">const</span> user = getUserLoggedInUser(); <span class="hljs-comment">// app specific function</span>

<span class="hljs-keyword">const</span> ability = defineAbilitiesOnOrganizationFor(user);


<span class="hljs-keyword">const</span> Organization = mongoose.model(<span class="hljs-string">'Organization'</span>, mongoose.Schema({
  email: <span class="hljs-built_in">String</span>,
  isAdmin: <span class="hljs-built_in">Boolean</span>, 
  phone: <span class="hljs-built_in">Number</span>,
  password: <span class="hljs-built_in">String</span>,
  content: <span class="hljs-built_in">String</span>,
  createdAt: <span class="hljs-built_in">Date</span>,
  firstName: <span class="hljs-built_in">String</span>,
  lastName: <span class="hljs-built_in">String</span>

}))

Organization.plugin(accessibleRecordsPlugin)

<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = mongoose.model(<span class="hljs-string">'Organization'</span>, Organization)
</code></pre><p>4).   Check abilities</p>
<pre><code><span class="hljs-comment">// ../controllers/organizationController.js</span>

<span class="hljs-keyword">const</span> { NotFound, Unauthorized, InternalServerError } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"http-errors"</span>);
<span class="hljs-keyword">const</span> Organizations = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../models/organization"</span>);

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">permissionChecker</span>(<span class="hljs-params">model, defineAbilitiesOnSubjectFor, userId</span>) </span>{
  <span class="hljs-keyword">try</span> {

   <span class="hljs-comment">// get user and all attributes</span>
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> model.findOne({ <span class="hljs-attr">_id</span>: userId });
    <span class="hljs-keyword">if</span> (!user) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotFound(<span class="hljs-string">"user not found"</span>);
    }
    <span class="hljs-comment">// get ability</span>
    <span class="hljs-keyword">const</span> ability = defineAbilitiesOnSubjectFor(user);
     <span class="hljs-keyword">return</span> ability;

  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Unauthorized(error.message);
  }
}

<span class="hljs-keyword">let</span> action = <span class="hljs-string">"update"</span> <span class="hljs-comment">// specify the type of action on the resource, to check for</span>
<span class="hljs-keyword">let</span> subject = <span class="hljs-string">"Organization"</span>  <span class="hljs-comment">// resource that needs permission and access control</span>

<span class="hljs-comment">// check for permission using the permission checker</span>
    <span class="hljs-keyword">const</span> ability = <span class="hljs-keyword">await</span> permissionChecker(
      Organization,
      defineAbilitiesOnOrganizationFor,
      user
    );

<span class="hljs-keyword">if</span>(ability) {
   <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> organization = <span class="hljs-keyword">new</span> Organization();
        organization.set(data); <span class="hljs-comment">// app specific data</span>
        ForbiddenError.from(ability).throwUnlessCan(action, organization);
        <span class="hljs-keyword">await</span> organization.save();
   }<span class="hljs-keyword">catch</span>(error){
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Unauthorized(error.message);
   }
} <span class="hljs-keyword">else</span>{
   <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InternalServerError(error.message);
}
</code></pre><p>Yea, it's that simple to implement a scaleable in memory permission management and resource authorization with CASL in your <a target="_blank" href="https://nodejs.org/en/">Nodejs</a> apps 😊.</p>
<p>CASL is progressively adaptable, which means you can start your project with the simple role-based authorization and evolve it later when your app functionality evolves.</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<h3 id="find-out-more">FIND OUT MORE</h3>
<ul>
<li><a target="_blank" href="https://casl.js.org/">CASL JS Documentation</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Role-Based Access Control ( RBAC) for NoSQL DB in Nodejs]]></title><description><![CDATA[Many RBAC (Role-Based Access Control) implementations differ, but the basics are widely adopted since it simulates real-life role (job) assignments.  - Onur Yıldırım.

Role-based access control (RBAC) is a method of modifying access and privileges ba...]]></description><link>https://blog.nextwebb.tech/role-based-access-control-rbac-for-nosql-db-in-nodejs</link><guid isPermaLink="true">https://blog.nextwebb.tech/role-based-access-control-rbac-for-nosql-db-in-nodejs</guid><category><![CDATA[Node.js]]></category><category><![CDATA[MongoDB]]></category><category><![CDATA[mongoose]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Sun, 03 Jan 2021 18:14:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609697410724/2Zeqr003Y.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Many RBAC (Role-Based Access Control) implementations differ, but the basics are widely adopted since it simulates real-life role (job) assignments.  - <em><a target="_blank" href="https://github.com/onury">Onur Yıldırım</a>.</em></p>
</blockquote>
<p>Role-based access control (RBAC) is a method of modifying access and privileges based on the roles of individual users or user groups within an organization.</p>
<p>RBAC lets members of an organization have access, to only the resources they need to do their jobs and restricts or prevents unlimited privileges to resources that don't pertain to them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609694922992/3FX3i9WX9.jpeg" alt="mimi-thian--VHQ0cw2euA-unsplash.jpg" /></p>
<h3 id="rbac-vs-abac">RBAC vs ABAC</h3>
<p>Role-based access control and attribute-based access control (ABAC) are both types of access control methods, but their approaches are different.
A use case that necessitates an RBAC system, would be if we intend to grant privileges on the application depending on the roles of users. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609695228781/3yXKadJbm.jpeg" alt="role-based-access-control-1024x536.jpeg" /></p>
<p>While ABAC grants access based on a combination of attributes, i.e., user attributes, resource attributes, attributes associated with the system or application to be accessed,  environmental attributes, and so on. For example, User attributes may include biodata information, uniquely identifies, role, security clearance, and etc.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1609695587263/C6VevgF1G.jpeg" alt="attribute-based-access-control-1024x536.jpeg" /></p>
<h3 id="back-story">Back Story</h3>
<p>On a recent project, my team was tasked with implementing a custom  Role-based access control framework for MongoDB(NoSQL DB), using the mongoose ORM schema.
In this access control framework we'll be considering 3 entities :</p>
<ol>
<li>Roles</li>
<li>User</li>
<li>Resources</li>
</ol>
<p><strong> Implementation context</strong></p>
<ul>
<li>The User has a Role</li>
<li>The Application has defined Resources and,</li>
<li>The role has privileges &amp; permissions to Resources </li>
</ul>
<p>let's write some  mongoose schema's for our collections 😄</p>
<p><iframe src="https://giphy.com/embed/jTiPtPeCXrHtzSY8be" width="480" height="400" class="giphy-embed"></iframe></p><p><a href="https://giphy.com/gifs/theoffice-jTiPtPeCXrHtzSY8be">via GIPHY</a></p><p></p>
<h3 id="schema">Schema</h3>
<p><strong> resources.models.js </strong></p>
<pre><code><span class="hljs-comment">/* RBAC - role-based access control
 first create roles; role =[ "user", "guest", "organization",  "superadmin" "globalsuperadmin"]
 next, create resouuce, add their roles_id and permmissions
 finally create users and indcate thier roles
*/</span>

    const mongooseClient = app.<span class="hljs-keyword">get</span>(<span class="hljs-string">'mongooseClient'</span>);
    const { <span class="hljs-keyword">Schema</span> } = mongooseClient;

    const resources = <span class="hljs-built_in">new</span> <span class="hljs-keyword">Schema</span>({
      <span class="hljs-type">name</span>: { <span class="hljs-keyword">type</span>: String, required: <span class="hljs-keyword">true</span> },
      slug: { <span class="hljs-keyword">type</span>: String, required: <span class="hljs-keyword">true</span> },
      resources_roles: [{
        roles_id: { <span class="hljs-keyword">type</span>: <span class="hljs-keyword">Schema</span>.<span class="hljs-keyword">Types</span>.ObjectId, <span class="hljs-keyword">ref</span> : <span class="hljs-string">'Roles'</span> },
        roles_name: { <span class="hljs-keyword">type</span>:  String },
        <span class="hljs-keyword">create</span>: { <span class="hljs-keyword">type</span>: <span class="hljs-type">Boolean</span> },
        <span class="hljs-keyword">delete</span>: { <span class="hljs-keyword">type</span>: <span class="hljs-type">Boolean</span> },
        <span class="hljs-keyword">update</span>: { <span class="hljs-keyword">type</span>: <span class="hljs-type">Boolean</span> },
        <span class="hljs-keyword">read</span>: { <span class="hljs-keyword">type</span>: <span class="hljs-type">Boolean</span> },
      }]
    }, {
      timestamps: <span class="hljs-keyword">true</span>
    });

    <span class="hljs-keyword">return</span> mongooseClient.model(<span class="hljs-string">'resources'</span>, resources);
</code></pre><p><strong> roles.models.js </strong></p>
<pre><code>

<span class="typescript">   <span class="hljs-keyword">const</span> mongooseClient = app.get(<span class="hljs-string">'mongooseClient'</span>);
    <span class="hljs-keyword">const</span> { Schema } = mongooseClient;

  <span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">app</span>) </span>{
    <span class="hljs-keyword">const</span> mongooseClient = app.get(<span class="hljs-string">'mongooseClient'</span>);
    <span class="hljs-keyword">const</span> { Schema } = mongooseClient;
    <span class="hljs-keyword">const</span> roles = <span class="hljs-keyword">new</span> Schema({
      name: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
      slug: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
    }, {
      timestamps: <span class="hljs-literal">true</span>
    });

    <span class="hljs-keyword">return</span> mongooseClient.model(<span class="hljs-string">'roles'</span>, roles);
  };</span>
</code></pre><p><strong> users.models.js</strong></p>
<pre><code>
 <span class="hljs-keyword">const</span> mongooseClient = app.get(<span class="hljs-string">'mongooseClient'</span>);
    <span class="hljs-keyword">const</span> { Schema } = mongooseClient;


  <span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">app</span>) </span>{
    <span class="hljs-keyword">const</span> mongooseClient = app.get(<span class="hljs-string">'mongooseClient'</span>);
    <span class="hljs-keyword">const</span> { Schema } = mongooseClient
    <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">new</span> mongooseClient.Schema({
      email: {<span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, unique: <span class="hljs-literal">true</span>, lowercase: <span class="hljs-literal">true</span>},
      password: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span> },
      first_name: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span> },
      last_name: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span> },
      roles: { <span class="hljs-keyword">type</span>: Schema.Types.ObjectId, ref : <span class="hljs-string">'Roles'</span> },

    }, {
      timestamps: <span class="hljs-literal">true</span>
    });

    <span class="hljs-keyword">return</span> mongooseClient.model(<span class="hljs-string">'users'</span>, users);
  };
</code></pre><p>Now when you want the permissions of a role on a resource, you just look up <code>role_id</code> and <code>resource_id</code> and check for which permissions are set to <code>true</code> in the <code>resources collection</code>.</p>
<h3 id="authorization-middleware">Authorization Middleware</h3>
<p><code>users.post('/', getAuth, someMethod)</code></p>
<p>Assuming you have a token of some sort on the request that identifies the user making the post, and attaching the user instance to the request object you can do this:</p>
<pre><code><span class="hljs-comment">// pseudo code</span>
getAuth = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">if</span>(req.user) { 

<span class="hljs-comment">// query to get the user role's  permissions for a resource</span>
    <span class="hljs-keyword">if</span>(token){
    <span class="hljs-comment">// handle jwt token authenticity and decrypt payload </span>

   <span class="hljs-comment">// get permission handler</span>
    db.getPerms({ role_id: req.user.role_id, resource_id: req.resource.id})
    .then((perms) =&gt; {
       <span class="hljs-keyword">var</span> allow = <span class="hljs-literal">false</span>;
       <span class="hljs-comment">// mapping of methods to permissions</span>
       perms.<span class="hljs-keyword">forEach</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">perm</span>)</span>{
           <span class="hljs-keyword">if</span> (req.method == <span class="hljs-string">"POST"</span> &amp;&amp; perms.create) allow = <span class="hljs-literal">true</span>;
           <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (req.method == <span class="hljs-string">"GET"</span> &amp;&amp; perms.read) allow = <span class="hljs-literal">true</span>;
           <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (req.method == <span class="hljs-string">"PUT"</span> &amp;&amp; perms.write) allow = <span class="hljs-literal">true</span>;
           <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (req.method == <span class="hljs-string">"DELETE"</span> &amp;&amp; perm.delete) allow = <span class="hljs-literal">true</span>;

       })
       <span class="hljs-keyword">if</span> (allow) next();
       <span class="hljs-keyword">else</span> {
            res.status(<span class="hljs-number">403</span>).send({<span class="hljs-built_in">error</span>: <span class="hljs-string">'access denied'</span>});
        }
    })
    .<span class="hljs-keyword">catch</span>((err)=&gt; {
       <span class="hljs-comment">//handle your reject and catch here</span>
    })
} <span class="hljs-keyword">else</span>{
    res.status(<span class="hljs-number">400</span>).send({<span class="hljs-built_in">error</span>: <span class="hljs-string">'invalid token'</span>})
}
</code></pre><p>This is some Pseudo-code, showing how the authentication middleware can be written. </p>
<p>This isn't the most optimized framework for implementing RBAC, so I'm looking forward to your feedback 😄!!</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
]]></content:encoded></item><item><title><![CDATA[Concurrency in Golang]]></title><description><![CDATA[A typical system is made up of smaller components and sub-systems that all work together.
Software programs are often made up of much smaller sub-programs as well.

For example, a web server such as Apache handles requests made from the web clients a...]]></description><link>https://blog.nextwebb.tech/concurrency-in-golang</link><guid isPermaLink="true">https://blog.nextwebb.tech/concurrency-in-golang</guid><category><![CDATA[Go Language]]></category><category><![CDATA[concurrency]]></category><category><![CDATA[backend]]></category><category><![CDATA[programming languages]]></category><category><![CDATA[100DaysOfCode]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Sun, 13 Dec 2020 17:59:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607880886693/nHrmecEnj.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A typical system is made up of smaller components and sub-systems that all work together.
Software programs are often made up of much smaller sub-programs as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607881454582/JYwuksvZ9.jpeg" alt="lucas-favre-4x-67z_TaGo-unsplash.jpg" /></p>
<p>For example, a web server such as <a target="_blank" href="https://httpd.apache.org/">Apache</a> handles requests made from the web clients and serves up static or dynamic content or HTML web pages as a response. Each request is handled like a small program, in a series of response and request cycles.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607868397792/7pOt3HsCY.webp" alt="What-is-a-Web-Server.webp" /></p>
<p>How ideal it'll be for large programs to run their smaller components independently and simultaneously  🤔 !?</p>
<blockquote>
<p><em>Making progress on more than one task simultaneously is known as concurrency.</em></p>
</blockquote>
<p>  <a target="_blank" href="https://golang.org/">Golang</a>, At the language level offers some great concurrency primitives and makes it extremely easy to implement a concurrent system.
Go has rich support for concurrency using </p>
<ul>
<li>Goroutines &amp;</li>
<li>Channels.</li>
</ul>
<h2 id="goroutines">Goroutines</h2>
<p> Goroutine always works in the background. They are functions executing concurrently with other goroutines in the same thread or set of threads. To create a goroutine we use the keyword <code>go</code> followed by a function invocation:</p>
<pre><code>package main

<span class="hljs-keyword">import</span> (
    "time"
    "fmt"
)

func say(s string){
    <span class="hljs-keyword">for</span> i:=<span class="hljs-number">0</span>; i &lt; <span class="hljs-number">3</span>; i++ {
        fmt.Println(s)
        <span class="hljs-type">time</span>.Sleep(<span class="hljs-type">time</span>.Microsecond*<span class="hljs-number">100</span>)
    }

} 
func main() {
    <span class="hljs-keyword">start</span> := <span class="hljs-type">time</span>.Now()

    go say("Hey")
    go say("There")

    elapsed := <span class="hljs-type">time</span>.Since(<span class="hljs-keyword">start</span>)
    fmt.Println(elapsed)

//the program runs <span class="hljs-keyword">concurrently</span>
}
</code></pre><p>Every program contains at least one  Goroutine and that Goroutine is known as the <strong>main Goroutine</strong>. </p>
<p>This program above consists of three goroutines. 
The first goroutine is implicit, it is the main function itself and all the goroutines are working under the <strong>main Goroutines </strong>.</p>
<p>The second and third goroutine is created when we invoke a function; In this case by calling <code>go say("Hey") &amp; go say("There")</code>. </p>
<p>By convention, when a function is invoked our program will execute all the statements within the function and then return to the next line following the invocation.
But with a goroutine, we return immediately to the next line and don't wait for the function to complete.</p>
<p> We can investigate this concurrency and the program time of execution by setting some delay.</p>
<p>A hack 👨‍💻 I found was to use the <code>sleep</code> method <code>time.Sleep(time.Second)</code> . </p>
<p> Without this, the program would exit before being given the opportunity to print all the strings  ''hey'' and "there". </p>
<p>Goroutines are lightweight and we can easily create many of them in our programs. We can modify the above program to run 20 😃goroutines by doing this:</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

start := time.Now()

  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++ {
    <span class="hljs-keyword">go</span> say(<span class="hljs-string">"Hey"</span>)
    <span class="hljs-keyword">go</span> say(<span class="hljs-string">"There"</span>)

  }

time.Sleep(time.Second)
elapsed := time.Since(start)
fmt.Println(elapsed)

}
</code></pre><h2 id="channels">Channels</h2>
<p>Channels are used for communication and sometimes synchronization between two executing goroutines.
Here is a sample program using channels:</p>
<pre><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"time"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-comment">// Channels are defined using the make function,</span>
    <span class="hljs-comment">// VariableName:= make(chan value-type)</span>

    ch := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-keyword">string</span>)

    <span class="hljs-comment">// this sends data to the channel</span>
    <span class="hljs-keyword">go</span> greeting(<span class="hljs-string">"Hello"</span>, ch)
    <span class="hljs-keyword">for</span> {
        <span class="hljs-comment">// We can indicate the direction of the data flow by using an arrow ch &lt;- data</span>

              msg := &lt;- ch
        <span class="hljs-comment">// and v:= &lt;- ch receives the data from the channel!</span>

        fmt.Println(msg)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">greeting</span><span class="hljs-params">(s <span class="hljs-keyword">string</span>, ch <span class="hljs-keyword">chan</span> <span class="hljs-keyword">string</span>)</span></span> {
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span>; i &lt;= <span class="hljs-number">5</span>; i++ {
        <span class="hljs-comment">//WE SEND A VALUE THROUGH A CHANNEL</span>
        ch &lt;- s
        time.Sleep(<span class="hljs-number">100</span> * time.Millisecond)
    }
    <span class="hljs-built_in">close</span>(ch)
}
</code></pre><p>The channel is created using the <code>make</code>function and the <code>chan</code> keyword, it is then passed on to the call to the <code>greeting</code> function which accepts a string and a channel of data-type string. The for loop is created in the main channel to receive and print out every incoming message. </p>
<p>The <code>close()</code> function indicates Closing a channel; thus no more values will be sent on it. This can be useful to communicate completion to the channel’s receivers.</p>
<p>So using a channel like this synchronizes the two goroutines.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I briefly talked about the essential concepts in Golang such as goroutines and channels, which are the implementations of concurrency in <a target="_blank" href="https://golang.org/">Go</a>.  I hope this article helps you learn more about <a target="_blank" href="https://golang.org/">Golang</a> features 😃. </p>
<p><strong>I enjoyed writing this article and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</strong></p>
<p>Do watch the <a target="_blank" href="https://www.youtube.com/watch?v=oV9rvDllKEg&amp;t=502s">Go-Routines</a> video by <em>Rob Pike</em> and have fun coding in Golang.</p>
<p>Until next time…</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a target="_blank" href="https://tour.golang.org/concurrency/1">Learn More about Go concurrency </a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How To Dockerize A Simple Django App]]></title><description><![CDATA[Introduction
According to Docker docs, 
Docker is a tool that provides OS-level virtualization to deliver software in packages called containers.
 Docker made application containers easy to use by providing a simplified, opinionated wrapper around ex...]]></description><link>https://blog.nextwebb.tech/how-to-dockerize-a-simple-django-app</link><guid isPermaLink="true">https://blog.nextwebb.tech/how-to-dockerize-a-simple-django-app</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[docker images]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Sun, 15 Nov 2020 16:05:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605454998267/xw-sj_QOu.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>According to Docker docs, 
<em>Docker is a tool that provides OS-level virtualization to deliver software in packages called containers.</em></p>
<p> Docker made application containers easy to use by providing a simplified, opinionated wrapper around existing Linux distribution invoked as containers, process control, security, and resource management technologies.</p>
<p>Docker excels in these two key areas:</p>
<ul>
<li>Packaging applications</li>
<li>Process isolation and management</li>
</ul>
<blockquote>
<p><em>Docker helps you package and run applications inside isolated little boxes with all platform and application dependencies provided, thus keeping the hosting computer nice and tidy.</em></p>
</blockquote>
<p>Docker solves problems associated with :</p>
<ul>
<li>Missing or incorrect application dependencies such as libraries, interpreters, code/binaries, users, Os packages, etc </li>
<li>Conflicts between programs running on the same host machine such as library dependencies or ports; Example: local Django trying to use port 80</li>
<li>Insufficient system resources required to run an application such as CPU and memory.</li>
</ul>
<p>Let’s see how Docker delivers on its promise to “Build, Ship, and Run” applications easily 
😃.</p>
<p><strong>Assumptions</strong>:  We'll be working with a <a target="_blank" href="https://www.django-rest-framework.org/">Django application</a>, Some experience working with a Django application might be needed and you have <a target="_blank" href="https://docs.docker.com/get-docker/">Docker</a> &amp; <a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a> installed.</p>
<h2 id="packaging-an-application-into-an-image">Packaging an Application Into An Image</h2>
<p>This OS-level virtualization provided by <a target="_blank" href="https://docs.docker.com/get-docker/">Docker</a> helps us mirror the application and platform into a <code>docker file</code>.
The <code>docker file</code> should be placed at the root of the Django project.</p>
<pre><code><span class="hljs-comment"># The first instruction is what image we want to base our container on</span>
<span class="hljs-comment"># We Use an official Python runtime as a parent image</span>
<span class="hljs-attribute">FROM</span> python:<span class="hljs-number">3</span>.<span class="hljs-number">8</span>.<span class="hljs-number">5</span> as production

<span class="hljs-comment"># The environment variable ensures that the python output is set straight</span>
<span class="hljs-comment"># to the terminal without buffering it first</span>
<span class="hljs-attribute">ENV</span> PYTHONUNBUFFERED <span class="hljs-number">1</span>

<span class="hljs-comment"># create a root directory for our project in the container</span>
<span class="hljs-attribute">RUN</span> mkdir /app

<span class="hljs-comment"># Set the working directory to /app</span>
<span class="hljs-attribute">WORKDIR</span> /app

<span class="hljs-comment"># Copy the current directory contents into the container at /app</span>
<span class="hljs-attribute">COPY</span> . /app/

<span class="hljs-comment"># Install any needed packages specified in requirements.txt</span>
<span class="hljs-attribute">RUN</span> pip install -r requirements.txt

<span class="hljs-comment"># tell the port number the container should expose</span>
<span class="hljs-attribute">EXPOSE</span> <span class="hljs-number">8000</span>
</code></pre><p>Docker images also allow you to define metadata and define more options to help operators run the application according to your application needs.</p>
<h2 id="setting-up-a-docker-composeyml-file">Setting Up A Docker-compose.yml File</h2>
<p>For clarity, Docker Compose solves the problem of running multi-container applications at once. You thus can set the desired amount of containers counts, their builds, services, and volumes, and then with a single set of commands, you can build, run, and configure all the containers.</p>
<blockquote>
<p>According to <a target="_blank" href="https://docs.docker.com/compose/">Docker-compose Docs</a>
Using Compose is basically a three-step process:  </p>
</blockquote>
<ul>
<li><p>Define your app’s environment with a Dockerfile so it can be reproduced anywhere.</p>
</li>
<li><p>Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.</p>
</li>
</ul>
<ul>
<li>Run docker-compose up and Compose starts and runs your entire app.</li>
</ul>
<p>it is also located in the root directory of the Django application.
The Docker environment variable file <code>.env</code> is necessary when you're creating complex containers or Database service for deployments.</p>
<p>So let's say you have your <code>.env file</code> (that's the full name of the file, by the way) located in the same directory that houses your <code>docker-compose.yml file</code>. In the <code>.env file</code>, you have on these lines: </p>
<pre><code><span class="hljs-attr">DATABASE_NAME</span>=postgresdb
<span class="hljs-attr">DATABASE_USER</span>=postgresuser
<span class="hljs-attr">DATABASE_PASSWORD</span>=postgresuser
<span class="hljs-attr">DATABASE_HOST</span>=db
<span class="hljs-attr">DATABASE_ENGINE</span>=django.db.backends.postgresql
<span class="hljs-attr">DATABASE_PORT</span>=<span class="hljs-number">5432</span>
</code></pre><p>A simple <code>docker-compose.yml</code> looks like this:</p>
<pre><code><span class="hljs-attribute">version</span>: '3.8'

<span class="yaml"><span class="hljs-comment"># This file defines two services: The db service and the web service.</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres</span>
    <span class="hljs-attr">environment:</span>
<span class="hljs-comment"># key-value pair for the environmental variable</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">environment:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"POSTGRES_USER:${DATABASE_USER}"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"POSTGRES_DB:${DATABASE_NAME}"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"POSTGRES_PASSWORD:${DATABASE_PASSWORD}"</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"POSTGRES_HOST:${DATABASE_HOST}"</span>
    <span class="hljs-attr">volumes:</span>
<span class="hljs-comment"># no data is lost  when we stop running the container, only for development</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./pgdata:/var/lib/postgresql/data</span>
    <span class="hljs-attr">ports:</span>
<span class="hljs-comment"># exposing port 5432 to the host machine</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"5432:5432"</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">&gt; 
        bash -c "
        python manage.py makemigrations
        &amp;&amp; python manage.py migrate 
        &amp;&amp; python manage.py runserver 0.0.0.0:8000
        "
</span>    <span class="hljs-attr">image:</span> <span class="hljs-string">appserver</span>
<span class="hljs-comment"># for development purposes, so when we make changes to the source code the change gets saved to the container.</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.:/appserver</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">ports:</span>
<span class="hljs-comment"># expose port 8001 to the host machine</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8001:8000"</span></span>
</code></pre><p>We're all set now, lets build and run our container using this simple  command :</p>
<pre><code>docker-compose up
</code></pre><p>That’s a simple  introduction to how application containers can help your team solve many common packaging, distribution, and operational problems; while helping you ship better software 
🤓.</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
<h3 id="further-reading">Further Reading</h3>
<ul>
<li><a target="_blank" href="https://docs.docker.com/compose/">Docker-compose documentation</a></li>
<li><a target="_blank" href="https://docs.docker.com/engine/reference/builder/">Docker documentation</a></li>
<li><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-20-04">Install docker on ubuntu 20.04</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introduction To Linked-list In Python]]></title><description><![CDATA[Introduction
There are situations when the allocation of memory to store data cannot be in a contiguous block of memory or stored sequentially in memory,
such situations include When :

You need constant insertions and deletions from the list (such a...]]></description><link>https://blog.nextwebb.tech/introduction-to-linked-list-in-python</link><guid isPermaLink="true">https://blog.nextwebb.tech/introduction-to-linked-list-in-python</guid><category><![CDATA[data structures]]></category><category><![CDATA[node]]></category><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Nextwebb]]></dc:creator><pubDate>Sun, 01 Nov 2020 21:29:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1604266088921/Mn7lPcqaB.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>There are situations when the allocation of memory to store data cannot be in a contiguous block of memory or stored sequentially in memory,
such situations include When :</p>
<ul>
<li>You need constant insertions and deletions from the list (such as in real-time computing where time predictability is absolutely critical).</li>
<li>You don't know how many items will be in the list.</li>
<li>You don't need random access to any elements</li>
<li>You want to be able to insert items in the middle of the list (such as a priority queue) and Et cetera.</li>
</ul>
<p>We implement data structures such as <code>linked list</code> with nodes. A node is simply a data element which consists of two parts: <em>data &amp; pointer to the next node.</em></p>
<p>It can consist of address data, geographical data, geometric data, routing information, or transaction details, etc. 
So nodes help us to know the address of the next data element from the values of the current data element. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1604261380071/tVOUfIaiX.png" alt="python-linked-lists-1.png" /></p>
<p><em>Figure 1: A sample implementation of a Single-linked list</em></p>
<p> These data elements called nodes are linked using pointers, hence called a <code>linked list</code> In Python.</p>
<h2 id="creation-of-nodes-for-a-single-linked-list">Creation of Nodes for a Single-Linked List</h2>
<p>Nodes are the foundations on which various other data structures <code>linked lists</code> and <code>trees</code> can be handled in Python.
The nodes are created by implementing a class that will hold the pointers along with the data element. </p>
<p>In the example below, we created a class named "ListNode" to hold the name of  Countries. 
The "next" pointer is initialized to null and three nodes and initialized with values as shown.</p>
<p>The next pointer of node1 points to node3 while the next pointer of node3 points to node2.</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Countries</span>(<span class="hljs-params">object</span>):</span>
    <span class="hljs-string">"""
    the default dataval is set to NIL
    """</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, data=None</span>):</span>
        self.data = data
        self.next = <span class="hljs-literal">None</span>

<span class="hljs-string">"""
 Instantiation of nodes
and setting the data part of the node
to the input value and the next pointer to NIL
"""</span>
node1 = Countries(<span class="hljs-string">'Nigeria'</span>)
node2 = Countries(<span class="hljs-string">'USA'</span>)
node3 = Countries(<span class="hljs-string">'Kenya'</span>)

<span class="hljs-string">"""
 Here we're setting the next pointer of node1 to point to node3, and the next pointer of node3 to point to node2
"""</span>
node1.next = node3
node3.next = node2
</code></pre><h2 id="traversing-the-node-elements">Traversing the Node Elements</h2>
<p>We can traverse the data elements/node of the <code>linked list</code> created above by creating a variable and assigning the first node to it. Then we use a while loop and the <code>next</code> pointer to print out all the node elements.</p>
<p><strong><em> Note that we have one more additional data element <code>node4</code> and the <code>next</code> pointers are properly arranged to get the output as countries orderly.</em></strong></p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Countries</span>(<span class="hljs-params">object</span>):</span>
    <span class="hljs-string">"""
    the default dataval is set to NIL
    """</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, data=None</span>):</span>
        self.data = data
        self.next = <span class="hljs-literal">None</span>

<span class="hljs-string">"""
 Instantiation of nodes
and setting the data part of the node
to the input value and the next pointer to NIL
"""</span>
node1 = Countries(<span class="hljs-string">'Nigeria'</span>)
node2 = Countries(<span class="hljs-string">'USA'</span>)
node3 = Countries(<span class="hljs-string">'Kenya'</span>)
node4 = Countries(<span class="hljs-string">'Canada'</span>)

<span class="hljs-string">"""
 Here we're setting the next pointer of node1 to point to node3,
 and the next pointer of node3 to point to node2
"""</span>
node1.next = node3
node3.next = node2
node2.next = node4


thisvalue = node1

<span class="hljs-keyword">while</span> thisvalue:
        print(thisvalue.data)
        thisvalue = thisvalue.next
</code></pre><p>When the above code is executed, the resulting output is given below:</p>
<pre><code><span class="hljs-attribute">Nigeria</span>
Kenya
USA
Canada
</code></pre><p>Additional operations like Adding Nodes, Removing Nodes, Searching the list and double-linked lists can be done by implementing appropriate methods using these node containers in the general data structures like linked lists and trees.</p>
<p>There you go with a practical introduction to linked-lists in Python 
😀.</p>
<p>Thanks for the audience and I hope you found this article helpful 🤗. Feel free to reach out to me on  <a target="_blank" href="https://github.com/nextwebb">Github</a>, <a target="_blank" href="https://twitter.com/i_am_nextwebb">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/in/peterson-oaikhenah-102645144/">LinkedIn</a>.
Do drop a like, comment, and share 😌.</p>
]]></content:encoded></item></channel></rss>