<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by David Fagbuyiro on Medium]]></title>
        <description><![CDATA[Stories by David Fagbuyiro on Medium]]></description>
        <link>https://medium.com/@davidfagb?source=rss-d1058d8a6759------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*tQTjIfY643Gzl6ovvctb4A.jpeg</url>
            <title>Stories by David Fagbuyiro on Medium</title>
            <link>https://medium.com/@davidfagb?source=rss-d1058d8a6759------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 26 May 2026 15:59:45 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@davidfagb/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[How to Build a Group Chat with Moderation Using React Native]]></title>
            <link>https://medium.com/@davidfagb/how-to-build-a-group-chat-with-moderation-using-react-native-6c25c2ec595c?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/6c25c2ec595c</guid>
            <category><![CDATA[messaging]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Mon, 03 Nov 2025 12:43:29 GMT</pubDate>
            <atom:updated>2025-11-03T12:43:29.136Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4tILuPZJoREGftil" /></figure><p>Building a safe and respectful group chat experience is more important than ever, especially in <a href="https://getstream.io/blog/video-game-chat-moderation/">online gaming communities</a> where conversations can scale quickly. A dedicated group moderator can make this easier by enforcing community standards, reducing toxic behavior, and keeping discussions welcoming for everyone.</p><p>With Stream’s AI Moderation built into the Chat API, moderation becomes even more seamless by automatically detecting harmful messages, filtering profanity, and supporting moderators in creating a positive environment for players.</p><blockquote>In this guide, you’ll learn how to combat a toxic community by building a group chat with an AI chat moderator. You’ll implement it using Stream’s <a href="https://getstream.io/moderation/">AI Moderation Service</a>, <a href="https://getstream.io/chat/">Chat API</a>, and <a href="https://getstream.io/chat/sdk/react-native/">React Native SDK</a> to review user messages and actively monitor them for harmful content. The goal isn’t to police users but to create a space where they can engage freely without worrying about spam, hate speech, or toxic behavior.</blockquote><h3>Prerequisites</h3><p>For this tutorial, you will need:</p><ul><li><a href="https://nodejs.org/en">Node.js</a> and <a href="https://docs.npmjs.com/">npm</a> are installed.</li><li><a href="https://www.npmjs.com/package/@react-native-community/cli">React Native CLI</a> installed.</li><li>A <a href="https://getstream.io/try-for-free/">free Stream account</a> to get your API key and secret.</li><li>An <a href="https://ngrok.com/">Ngrok</a> account to expose your local server to Stream webhooks.</li></ul><h3>Building the Group Chat</h3><blockquote>Once you’ve completed all the prerequisites, you’ll set up a basic group chat app using React Native and Stream. This app will include the essential components for a chat experience: a channel list, a message list, a message composer, and a new group creation flow.</blockquote><p>Start by creating a new folder to store all your project files. A good place for this is your desktop, as it’s easy to access. Inside this folder, create a new file named \.env\.</p><p>Next, copy and paste the following code into your newly created \.env\ file. This file will hold important configuration details for your app and token server.</p><pre>STREAM\_KEY=YOUR\_STREAM\_KEY  <br>STREAM\_SECRET=YOUR\_STREAM\_SECRET  <br>PORT=5050  </pre><p>The \.env\ file is where you save your Stream credentials, which consists of your Stream key and your Stream secret key found on your account dashboard, as seen in the image below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*qLA1r55JYQ88uGM8" /></figure><p>Create a new file called \server.js\ in the main project folder. Then, please copy and paste the following code into it.</p><pre>import &#39;dotenv/config&#39;;<br>import express from &#39;express&#39;;<br>import cors from &#39;cors&#39;;<br>import { StreamChat } from &#39;stream-chat&#39;;<br><br>const app = express();<br><br>app.use(cors());<br>app.use(express.json());<br><br>const STREAM_KEY = process.env.STREAM_KEY;<br>const STREAM_SECRET = process.env.STREAM_SECRET;<br>const PORT = process.env.PORT || 5050;<br><br>if (!STREAM_KEY || !STREAM_SECRET) {<br>  console.error(&#39;Missing STREAM_KEY or STREAM_SECRET in .env&#39;);<br>  process.exit(1);<br>}<br><br>const serverClient = StreamChat.getInstance(STREAM_KEY, STREAM_SECRET);<br><br>app.post(&#39;/token&#39;, async (req, res) =&gt; {<br>  try {<br>    const { user_id } = req.body || {};<br><br>    if (!user_id) {<br>      return res.status(400).json({ error: &#39;user_id required&#39; });<br>    }<br><br>    const token = serverClient.createToken(user_id);<br>    await serverClient.upsertUser({ id: user_id, name: user_id });<br><br>    res.json({<br>      token,<br>      user: { id: user_id, name: user_id },<br>      api_key: STREAM_KEY<br>    });<br>  } catch (err) {<br>    console.error(err);<br>    res.status(500).json({ error: &#39;failed_to_issue_token&#39; });<br>  }<br>});<br><br>app.listen(PORT, () =&gt; {<br>  console.log(`Token server running at http://localhost:${PORT}`);<br>});</pre><p>Create another file called \package.json\ in the main project folder. Then, copy and paste the following code into it.</p><pre>{<br><br>  &quot;name&quot;: &quot;chat-token-server&quot;,<br><br>  &quot;private&quot;: true,<br><br>  &quot;type&quot;: &quot;module&quot;,<br><br>  &quot;dependencies&quot;: {<br><br>    &quot;cors&quot;: &quot;^2.8.5&quot;,<br><br>    &quot;dotenv&quot;: &quot;^16.4.5&quot;,<br><br>    &quot;express&quot;: &quot;^4.19.2&quot;,<br><br>    &quot;stream-chat&quot;: &quot;^9.18.1&quot;<br><br>  },<br><br>  &quot;scripts&quot;: {<br><br>    &quot;start&quot;: &quot;node index.js&quot;<br><br>  }<br><br>}</pre><h3>Creating a New React Native Project</h3><p>After setting up your \server.js\ and \.env\ files, you’ll need to create the group chat&#39;s user interface.</p><p>To build the UI, go to your main project folder and create a new React Native project. You can do this by running the following commands in your terminal from the project’s root folder.</p><pre>npx create-expo-app rn-stream-chat</pre><h3>Installing Necessary Dependencies</h3><p>Next, you’ll install the <a href="https://getstream.io/chat/sdk/react-native/">Stream Chat React Native SDK</a> and its dependencies.</p><pre>cd rn-stream-chat<br><br>npm i stream-chat stream-chat-react-native react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-svg</pre><blockquote>​​Note that the RN SDK wraps the JS client and keeps versions compatible. The Stream Docs warn against manually installing stream-chat to avoid version skew, but if you do, pin to a compatible version and don’t duplicate it. If you hit weird client issues, remove your explicit stream-chat dep and let the RN SDK supply it.</blockquote><p>Create a <a href="https://github.com/Davidfagb/Group-chat-with-Stream-and-ReactNative">file</a> named babel.config.js and paste the code below into it.</p><pre>module.exports \= function(api) {<br><br>  api.cache(true);<br><br>  return {<br><br>    presets: \[&#39;babel-preset-expo&#39;\],<br><br>    plugins: \[&#39;react-native-reanimated/plugin&#39;\],<br><br>  };<br><br>};</pre><p>Create a new folder called src to store your project files. Inside src, add a subfolder named stream, then create a file called client.ts and paste the following code into it.</p><pre>import { StreamChat } from &#39;stream-chat&#39;;<br><br>let client: StreamChat | null \= null;<br><br>export function getStreamClient(apiKey: string) {<br><br>  if (\!client) client \= StreamChat.getInstance(apiKey);<br><br>  return client;<br><br>}</pre><p>Next, you will create a new folder inside the \src\ folder named \screens\, then create a file \/LoginScreen.tsx\, and paste the code below inside.</p><pre>import React, { useState } from &#39;react&#39;;<br><br>import { View, Text, TextInput, Button, Alert, Platform } from &#39;react-native&#39;;<br><br>type Props \= {<br><br>  onLoggedIn: (args: { userId: string, token: string, apiKey: string }) \=\&gt; void;<br><br>};<br><br>export default function LoginScreen({ onLoggedIn }: Props) {<br><br>  const \[userId, setUserId\] \= useState(&#39;joel&#39;);<br><br>  const \[serverUrl, setServerUrl\] \= useState(<br><br>    Platform.select({ ios: &#39;http://127.0.0.1:5050&#39;, default: &#39;http://10.0.2.2:5050&#39; })\!<br><br>  );<br><br>  const login \= async () \=\&gt; {<br><br>    try {<br><br>      const res \= await fetch(\`${serverUrl}/token\`, {<br><br>        method: &#39;POST&#39;,<br><br>        headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },<br><br>        body: JSON.stringify({ user\_id: userId })<br><br>      });<br><br>      const data \= await res.json();<br><br>      if (\!res.ok) throw new Error(data?.error || &#39;Failed&#39;);<br><br>      onLoggedIn({ userId, token: data.token, apiKey: data.api\_key });<br><br>    } catch (e:any) {<br><br>      Alert.alert(&#39;Login failed&#39;, e.message);<br><br>    }<br><br>  };<br><br>  return (<br><br>    \&lt;View style={{ flex: 1, padding: 20, gap: 12, justifyContent: &#39;center&#39; }}\&gt;<br><br>      \&lt;Text style={{ fontSize: 22, fontWeight: &#39;600&#39; }}\&gt;Sign in\&lt;/Text\&gt;<br><br>      \&lt;Text\&gt;Pick a username (must match a seeded user or any string):\&lt;/Text\&gt;<br><br>      \&lt;TextInput<br><br>        value={userId}<br><br>        onChangeText={setUserId}<br><br>        placeholder=&quot;e.g. joel&quot;<br><br>        style={{ borderWidth: 1, borderColor: &#39;\#ccc&#39;, borderRadius: 8, padding: 12 }}<br><br>        autoCapitalize=&quot;none&quot;<br><br>      /\&gt;<br><br>      \&lt;Text\&gt;Token server URL\&lt;/Text\&gt;<br><br>      \&lt;TextInput<br><br>        value={serverUrl}<br><br>        onChangeText={setServerUrl}<br><br>        placeholder=&quot;http://10.0.2.2:5050 (Android Emulator)&quot;<br><br>        style={{ borderWidth: 1, borderColor: &#39;\#ccc&#39;, borderRadius: 8, padding: 12 }}<br><br>        autoCapitalize=&quot;none&quot;<br><br>      /\&gt;<br><br>      \&lt;Button title=&quot;Continue&quot; onPress={login} /\&gt;<br><br>    \&lt;/View\&gt;<br><br>  );<br><br>}</pre><p>The next step is to create a new file named \ChannelScreen.tsx\ inside the \src/screens\ folder and paste the code below.</p><pre>import React, { useEffect, useState } from &#39;react&#39;;<br><br>import { ActivityIndicator, View } from &#39;react-native&#39;;<br><br>import { Channel, MessageList, MessageInput, useChatContext } from &#39;stream-chat-expo&#39;;<br><br>import type { StackScreenProps } from &#39;@react-navigation/native-stack&#39;;<br><br>type RootStackParamList \= {<br><br>  ChannelList: undefined;<br><br>  Channel: { channelId: string };<br><br>  NewGroup: undefined;<br><br>};<br><br>type Props \= StackScreenProps\&lt;RootStackParamList, &#39;Channel&#39;\&gt;;<br><br>export default function ChannelScreen({ route }: Props) {<br><br>  const { client } \= useChatContext();<br><br>  const \[chan, setChan\] \= useState\&lt;any\&gt;(null);<br><br>  useEffect(() \=\&gt; {<br><br>    let mounted \= true;<br><br>    (async () \=\&gt; {<br><br>      const c \= client.channel(&#39;messaging&#39;, route.params.channelId);<br><br>      await c.watch();<br><br>      if (mounted) setChan(c);<br><br>    })();<br><br>    return () \=\&gt; { mounted \= false; };<br><br>  }, \[client, route.params.channelId\]);<br><br>  if (\!chan) return \&lt;View style={{ flex:1, justifyContent:&#39;center&#39;, alignItems:&#39;center&#39; }}\&gt;\&lt;ActivityIndicator /\&gt;\&lt;/View\&gt;;<br><br>  return (<br><br>    \&lt;Channel channel={chan}\&gt;<br><br>      \&lt;MessageList /\&gt;<br><br>      \&lt;MessageInput /\&gt;<br><br>    \&lt;/Channel\&gt;<br><br>  );<br><br>}</pre><p>Finally, you’ll create a file named \app.tsx\ in the chat-group folder and paste in the code below. This code writes everything about the login and connects the Stream to the screens.</p><pre>import React, { useEffect, useState } from &#39;react&#39;;<br><br>import { SafeAreaView, StatusBar, View, ActivityIndicator } from &#39;react-native&#39;;<br><br>import { NavigationContainer } from &#39;@react-navigation/native&#39;;<br><br>import { createNativeStackNavigator } from &#39;@react-navigation/native-stack&#39;;<br><br>import { OverlayProvider, Chat, useChatContext } from &#39;stream-chat-expo&#39;;<br><br>import { getStreamClient } from &#39;./src/stream/client&#39;;<br><br>import LoginScreen from &#39;./src/screens/LoginScreen&#39;;<br><br>import ChannelListScreen from &#39;./src/screens/ChannelListScreen&#39;;<br><br>import ChannelScreen from &#39;./src/screens/ChannelScreen&#39;;<br><br>import NewGroupScreen from &#39;./src/screens/NewGroupScreen&#39;;<br><br>type RootStackParamList \= {<br><br>  ChannelList: undefined;<br><br>  Channel: { channelId: string };<br><br>  NewGroup: undefined;<br><br>};<br><br>const Stack \= createNativeStackNavigator\&lt;RootStackParamList\&gt;();<br><br>function ChatStack() {<br><br>  return (<br><br>    \&lt;Stack.Navigator\&gt;<br><br>      \&lt;Stack.Screen name=&quot;ChannelList&quot; component={ChannelListScreen} options={{ title: &#39;Groups&#39; }} /\&gt;<br><br>      \&lt;Stack.Screen name=&quot;NewGroup&quot; component={NewGroupScreen} options={{ title: &#39;New Group&#39; }} /\&gt;<br><br>      \&lt;Stack.Screen name=&quot;Channel&quot; component={ChannelScreen} options={{ title: &#39;Chat&#39; }} /\&gt;<br><br>    \&lt;/Stack.Navigator\&gt;<br><br>  );<br><br>}<br><br>function ConnectedChat({ apiKey, userId, token }: { apiKey: string; userId: string; token: string }) {<br><br>  const \[ready, setReady\] \= useState(false);<br><br>  const client \= getStreamClient(apiKey);<br><br>  useEffect(() \=\&gt; {<br><br>    let mounted \= true;<br><br>    (async () \=\&gt; {<br><br>      await client.connectUser({ id: userId, name: userId }, token);<br><br>      if (mounted) setReady(true);<br><br>    })();<br><br>    return () \=\&gt; {<br><br>      mounted \= false;<br><br>      client.disconnectUser();<br><br>    };<br><br>  }, \[apiKey, userId, token\]);<br><br>  if (\!ready) return \&lt;View style={{ flex:1, justifyContent:&#39;center&#39;, alignItems:&#39;center&#39; }}\&gt;\&lt;ActivityIndicator /\&gt;\&lt;/View\&gt;;<br><br>  return (<br><br>    \&lt;OverlayProvider\&gt;<br><br>      \&lt;Chat client={client}\&gt;<br><br>        \&lt;ChatStack /\&gt;<br><br>      \&lt;/Chat\&gt;<br><br>    \&lt;/OverlayProvider\&gt;<br><br>  );<br><br>}<br><br>export default function App() {<br><br>  const \[session, setSession\] \= useState\&lt;{ apiKey: string; userId: string; token: string } | null\&gt;(null);<br><br>  return (<br><br>    \&lt;SafeAreaView style={{ flex: 1 }}\&gt;<br><br>      \&lt;StatusBar /\&gt;<br><br>      \&lt;NavigationContainer\&gt;<br><br>        {session ? (<br><br>          \&lt;ConnectedChat apiKey={session.apiKey} userId={session.userId} token={session.token} /\&gt;<br><br>        ) : (<br><br>          \&lt;LoginScreen onLoggedIn={setSession} /\&gt;<br><br>        )}<br><br>      \&lt;/NavigationContainer\&gt;<br><br>    \&lt;/SafeAreaView\&gt;<br><br>  );<br><br>}</pre><h3>Test the Application</h3><p>Now that you’ve built the group chat, the next step is to run the application. To do so, you’ll start the token server following the steps below.</p><p>Navigate to the root folder where the \server.s\ is located and run the command below.</p><pre>npm start</pre><p>After running the command, your terminal should display something related to the screenshot below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/876/0*pzC5IaDqU-pnR7Bs" /></figure><p>Finally, open a separate terminal, navigate to the \chat-group\ folder, and run the command below to start the iOS simulator.</p><pre>npm start</pre><blockquote>To run the app, launch it on either an iOS Simulator by pressing “i” or an Android Emulator by pressing “a”. The token server URL is already set by default for both: <a href="http://10.0.2.2:5050/">http://10.0.2.2:5050</a> for Android and <a href="http://127.0.0.1:5050/">http://127.0.0.1:5050</a> for iOS.</blockquote><p>Once in the app, log in as Alice or any of the other seeded users. To start a new chat, tap on a new group, select at least two users from the list, and then tap create. You can now begin chatting within the new group.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/666/0*6mOv0l1SMGEXIsOQ" /></figure><p>Below is a screenshot from the group chat before implementing the Stream AI moderator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/678/0*M73qy8BFSCPodtAE" /></figure><blockquote>From the screenshot above, you can see several abusive slurs and profanity words being used, but none are flagged or blocked. That’s because you have not introduced the moderator yet.</blockquote><h3>Introduction to Group Chat Moderation with Stream</h3><blockquote>Stream’s <a href="https://getstream.io/moderation/">AI-powered moderation</a> API works natively with Stream’s Chat, Video, and Activity Feeds APIs, and it also works with inhouse experiences to automatically detect and act on a wide range of harmful content across text, images, live video, and audio.</blockquote><p>Here’s a condensed list of some supported harms and categories:</p><ul><li>Spam: unsolicited or repetitive messages.</li><li>Hatred: abusive or threatening language.</li><li>Racism: discriminatory or prejudiced comments based on race or ethnicity.</li><li>LGBTQIA+ phobia: negative or hostile comments towards LGBTQIA+ individuals.</li><li>Misogyny: comments that express hatred or prejudice against women.</li><li>Ableism: discriminatory or prejudiced comments based on physical or mental disabilities.</li><li>Threat: statements expressing an intention to cause harm or violence to someone.</li><li>Terrorism &amp; violent extremism: support or promotion of terrorist activities or ideologies.</li><li>Pedophilia: comments that express sexual interest in minors.</li><li>Scam: fraudulent schemes or deceptive practices.</li><li>Flood: excessive or repetitive posting of messages.</li><li>Forbidden link: sharing links to prohibited or harmful websites.</li></ul><p>Stream’s moderation system works by analyzing messages in real-time and taking action based on <a href="https://getstream.io/moderation/docs/dashboard/configuring-policy/">configurable policies</a>. You can choose to:</p><ul><li>Flag messages: Mark messages for review by a human moderator.</li><li>Block messages: Prevent other users from seeing your messages.</li><li><a href="https://getstream.io/glossary/ban/">Ban</a> users: Temporarily or permanently block users from sending messages.</li><li>Shadow block.</li><li>Bounce and block.</li><li>Bounce and flag.</li></ul><h3>Using Advanced AI Moderation Engines</h3><p>While flagging, blocking, and banning are foundational tools in moderating group chat, they lack the nuance and adaptability needed for large and dynamic communities. Stream’s AI-powered moderation goes beyond these simple actions, offering sophisticated engines that understand intent, context, and subtlety.</p><p>Here’s what you gain:</p><h4>AI Text Harm Detection (LLM)</h4><blockquote>The <a href="https://getstream.io/moderation/docs/engines/ai-text/">AI text harm</a> detection uses Large Language Models to understand context and user intent, instead of only looking for profane words. It can catch subtle or indirect harmful content, like passive-aggressive remarks or harassment, that keyword filters often miss.</blockquote><p>Here’s how it works:</p><p>As a developer, you can set up a few key parameters. First is the app context, which explains what your application is about. This helps the AI understand the background of the conversations. Next are the LLM rules, where you define harm labels and what they mean in your app. For example, you can explain what counts as “scam” or “hate speech” for your community. Stream then combines your app context, your rules, and the last few messages in a conversation.</p><p>With a bit of prompt engineering, it sends this to the LLM and asks it to classify the message based on the rules you provided.</p><p>Configuration:</p><p>Get started! Activate your <a href="https://getstream.io/try-for-free/">free Stream account today</a> and start prototyping your chat app.</p><p>You can set up the LLM moderation engine directly in the Stream dashboard. Follow these steps:</p><ul><li>Log in to your <a href="https://dashboard.getstream.io/">Stream dashboard</a>.</li><li>Go to Moderation in the sidebar.</li><li>Open Policies.</li><li>Select the policy you want to update.</li><li>Click on AI (LLM) Text.</li></ul><p>From here, you can choose harm categories, set actions like bounce and flag, bounce and block, and adjust sensitivity levels.</p><p>See the screenshot below for guidance on what it looks like.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7iN9kymn15MujBrO" /></figure><h4>AI Text Harm Detection (NLP) with Blocklists and Regex Filtering</h4><blockquote>A layered approach works best. Blocklists such as profanity_en_2020_v1 and regex rules give you fast and predictable filtering, while the <a href="https://getstream.io/moderation/docs/engines/ai-text/">NLP engine</a> adds extra understanding of meaning. This makes it easier to tell the difference between harmless mentions and real violations.</blockquote><p>Configuration:</p><p>To set up the AI Harm Engine, go to the AI Text section in your moderation policy. You will see a list of harm categories that the AI can detect, such as harassment, hate speech, spam, and explicit content.</p><p>For each category, you can choose what should happen: flag the message, block it, or shadow block it. You can also change the sensitivity level for different types of harmful content. This helps you fine-tune moderation to match your community.</p><p>It is usually best to start with lighter settings and then adjust as you learn more about how people in your community interact.</p><h4>Severity Levels</h4><p>The AI Text moderation engine can label harmful content as Low, Medium, High, or Critical. These levels are mainly used for toxic categories like hate speech or sexual harassment.</p><p>For each severity level, you can choose what action should happen. For example, flag, block, or shadow block.</p><p>See the screenshot below for how this looks in the dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4kSV1jpxOahkUpWh" /></figure><h3>Setting up Stream and Enabling Moderation</h3><blockquote>Now that your project is set up, the next step is configuring Stream and enabling the AI moderation features. <a href="https://getstream.io/try-for-free/">Create your free account</a> to log in to your Stream dashboard and create a new application. You’ll find your API key and secret in the app’s settings.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*RPiUa3aCFYswNpwN" /></figure><p>You can manage policies entirely in the dashboard with the lowest friction. Set rules (AI text/image engines, blocklists) and apply them to chat via config keys like \chat:messaging\, all messaging channels, and Stream. The dashboard lets you monitor the content flagged by your configured policies and provides tools to fine-tune those policies based on real data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6t2x7ClcCeyAa3yb" /></figure><h3>Setting up Moderation Policy</h3><blockquote>Within your Stream dashboard, you can configure your chat moderation settings. Begin by selecting the Chat section, then open the Moderation tab. From this screen, you’ll be able to enable Stream’s built-in moderation tools and apply the profanity_en_2020_v1 blocklist to filter out profanity.</blockquote><p>For this tutorial, you will enable all the moderation categories and set the action to flag. This will automatically flag any harmful messages sent to the group chat.</p><p>Later, you can switch to the ‘block’ option by changing your Stream dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ubnu4lhgWpe4FFb9" /></figure><h3>Implementing Moderation in the Group Chat</h3><p>To add moderation to your group chat, you need to set up rules in your Stream dashboard. This allows you to take action against harmful content such as threats, self-harm, harassment, etc from the moderation/policies section.AI moderation policy and set the AI: Text to block</p><h3>The Main App Component (Updated App.tsx)</h3><p>The main \*App.tsx\* file will handle the initialization of the Stream client and the navigation between the channel list and the chat screen.</p><p>When you switch users, connecting and disconnecting should be clean. The server’s webhook will post a single notice so you won’t see duplicate messages. For a smooth user experience, you can use \additionalFlatListProps\ to help users scroll to older messages easily.</p><pre>import React, { useEffect, useState, useCallback } from &#39;react&#39;;<br><br>import { SafeAreaView } from &#39;react-native&#39;;<br><br>import {<br><br>  Chat,<br><br>  ChannelList,<br><br>  Channel,<br><br>  MessageList,<br><br>  MessageInput,<br><br>  OverlayProvider,<br><br>} from &#39;stream-chat-react-native&#39;;<br><br>import { StreamChat } from &#39;stream-chat&#39;;<br><br>const backend \= &#39;http://localhost:5050&#39;; <br><br>const chatClient \= StreamChat.getInstance(&#39;dummy&#39;); <br><br>export default function App() {<br><br>  const \[ready, setReady\] \= useState(false);<br><br>  const \[activeChannel, setActiveChannel\] \= useState\&lt;any\&gt;(null);<br><br>  const userId \= &#39;alice&#39;;<br><br>  useEffect(() \=\&gt; {<br><br>    (async () \=\&gt; {<br><br>      const resp \= await fetch(\`${backend}/token?user\_id=${userId}\`).then(r \=\&gt; r.json());<br><br>      const client \= StreamChat.getInstance(resp.apiKey);<br><br>      await client.connectUser(<br><br>        {<br><br>          id: userId,<br><br>          name: &#39;Alice&#39;,<br><br>          image: &#39;https://getstream.io/random\_png/?id=alice\&amp;name=Alice&#39;,<br><br>        },<br><br>        resp.token<br><br>      );<br><br>      await fetch(\`${backend}/channel\`, {<br><br>        method: &#39;POST&#39;,<br><br>        headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },<br><br>        body: JSON.stringify({ channelId: &#39;demo-general&#39;, members: \[&#39;alice&#39;, &#39;bob&#39;\] }),<br><br>      });<br><br>      (global as any).chatClient \= client; <br><br>      setReady(true);<br><br>    })();<br><br>    return () \=\&gt; {<br><br>      const c \= (global as any).chatClient;<br><br>      if (c) c.disconnectUser();<br><br>    };<br><br>  }, \[\]);<br><br>  const onSend \= useCallback(async (message: any) \=\&gt; {<br><br>    try {<br><br>      await activeChannel.sendMessage(message);<br><br>    } catch (e: any) {<br><br>      alert(e?.message ?? &#39;Message rejected by moderation rules.&#39;);<br><br>    }<br><br>  }, \[activeChannel\]);<br><br>  if (\!ready) return null;<br><br>  return (<br><br>    \&lt;OverlayProvider\&gt;<br><br>      \&lt;SafeAreaView style={{ flex: 1 }}\&gt;<br><br>        \&lt;Chat client={(global as any).chatClient}\&gt;<br><br>          {activeChannel ? (<br><br>            \&lt;Channel channel={activeChannel}\&gt;<br><br>              \&lt;MessageList /\&gt;<br><br>              \&lt;MessageInput onSend={onSend} /\&gt;<br><br>            \&lt;/Channel\&gt;<br><br>          ) : (<br><br>            \&lt;ChannelList<br><br>              filters={{ type: &#39;messaging&#39;, members: { $in: \[&#39;alice&#39;\] } }}<br><br>              sort={{ last\_message\_at: \-1 }}<br><br>              onSelect={(ch) \=\&gt; setActiveChannel(ch)}<br><br>            /\&gt;<br><br>          )}<br><br>        \&lt;/Chat\&gt;<br><br>      \&lt;/SafeAreaView\&gt;<br><br>    \&lt;/OverlayProvider\&gt;<br><br>  );<br><br>}</pre><h3>Test the Application</h3><p>To test the application, you’ll first navigate to the root folder or the backend folder where the \server.js\ is located, then open the terminal and paste the command below:</p><pre>node server.js</pre><p>After running the command below, you should see something related to the screenshot below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IrVQAjz673B-qv1I" /></figure><p>Second, you have to open a new terminal different from the above to start Ngrok. Before you continue to run the application, you can copy the commands below to run the server.</p><pre>ngrok http 5050</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ABE-_Iek1vcpri6m" /></figure><p>Copy the \https://e353facdfb71.ngrok-free.app/webhook\ URL and paste it in your Stream webhook event and configuration. Make sure you tick the box labeled “subscribe to all current and future events,” then click Submit to save the webhook, as shown in the screenshot below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SwN_kK7-5f-NtwgQ" /></figure><p>Then open another terminal and navigate to the Stream Chat folder from the root folder by pasting the command below:</p><pre>cd rn-stream-chat</pre><p>Copy the command below to start the emulator:</p><pre>npx expo start \-c  </pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vAlwm8LFgusLdQ27" /></figure><p>After running the command above, you will get a list of commands. Just reply with the letter ‘I’, and it will open the emulator window, where you will see the group chat members list.</p><h3>The Chat List Screen</h3><p>The chat list screen will display a list of available members. You can select a chat name or create a custom name to join the conversation in the group chat.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/666/0*keCksFQUJYit_mFp" /></figure><h3>The Chat Screen</h3><p>The chat screen is where the magic happens. This is where users can send and receive messages. Our AI moderator will be at work, removing spam messages before they’re delivered — as seen in the screenshot below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KbpfqXCYrVvR241_" /></figure><h3>Testing the AI Moderator</h3><p>Once you have the application running, you can test the AI moderator by sending messages that violate the rules you’ve configured in your Stream dashboard. For example, you can try sending words from the \profanity\_2020\ list.</p><p>You should see that these messages are automatically blocked and not visible to other users in the chat, as shown in the short video below and in the Stream Moderation dashboard screenshot.</p><p>Below is a new test with two different iOS emulators.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*p1VMlV06t-ks9fF9" /></figure><p>Below is a screenshot from the chat explorer dashboard showing the allowed and blocked messages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_Er_qRX3Y20VWxvf" /></figure><h3>What’s Next?</h3><p>You now have a fully functional React Native group chat that uses Stream to detect and block harmful or inappropriate messages in real time. The app securely connects users, allows seamless user switching without crashes, and loads older conversations smoothly as you scroll.</p><blockquote>When <a href="https://getstream.io/blog/build-chat-messaging-app/">building a chat app</a>, it’s your responsibility to create a safe and respectful environment where your community can thrive. Without proper moderation, toxicity, spam, and harassment can quickly overwhelm online spaces, driving users away.</blockquote><p>Stream’s AI Moderation API makes it easier for developers to scale communities responsibly by automatically detecting and filtering harmful content, reducing the burden on human moderators, and helping maintain a positive experience for all participants.</p><blockquote>Check out the <a href="https://github.com/Saintdavidking/Group-chat-using-Stream-and-ReactNative">GitHub repository</a> for the complete code.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6c25c2ec595c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How AI Agents Are Changing the Future of Web Scraping]]></title>
            <link>https://medium.com/@davidfagb/how-ai-agents-are-changing-the-future-of-web-scraping-a19f836ae803?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/a19f836ae803</guid>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Tue, 14 Oct 2025 16:03:40 GMT</pubDate>
            <atom:updated>2025-10-14T16:08:30.183Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Future of Web Scraping Cover image" src="https://cdn-images-1.medium.com/max/800/0*DnwbsCnISiZBXxjM.png" /><figcaption>Future of Web Scraping</figcaption></figure><p>In today’s world of data collection, intelligent AI agents are transforming how we approach data. Instead of using fixed scripts that stop working when a webpage changes, these agents can think, understand, and adjust, making data extraction stronger, more accurate, and smarter.</p><p>In this article, we examine how these ing paradigms and solutions, such as [OloStep](https://www.olostep.com/), are capitalizing on agents as they shift.</p><h4>From Static Scrapers to Adaptive Agents</h4><p>Traditional web scrapers are built on fixed rules and selectors. They expect pages to follow consistent HTML structures and often fail when sites update. Autonomous agents instead observe patterns, reason about structure, perform corrective actions, and recover from failures. They can decide when to click a “load more” button or when to scroll further. They can detect missing fields or empty responses and retry or choose an alternate path.<br>Because these agents act based on high-level goals rather than brittle scripts, they can survive changes in page layout and dynamic content more gracefully. They are more robust to JavaScript rendering, infinite scroll, asynchronous loading, and unexpected content shifts.</p><h4>Why Web Scraping Matters for AI Agents</h4><figure><img alt="Why Web Scraping Matters for AI Agents" src="https://cdn-images-1.medium.com/max/800/0*1-8sgv2xSsSuSWbf.png" /><figcaption>Why Web Scraping Matters for AI Agents</figcaption></figure><p>AI agents need fresh data. They cannot operate solely on training data or outdated snapshots. Scraped web data gives them the real-time sight and sound of the internet. It enables:</p><ul><li>Sensing changing prices, new product listings, or shifts in sentiment</li><li>Validating assumptions with live examples</li><li>Triggering downstream workflows or actions based on updated facts</li></ul><p>Autonomous agents transform web scraping from a background utility into a primary component. The faster the data, the more intelligent the agent.</p><h4>How OloStep Enables Smarter Agents</h4><p>[OloStep](docs.olostep.com) presents itself as a unified web data API built for AI and research agents. It supports crawling, parsing, routing, automation, and workflows behind the scenes. It exposes features such as click and form fill, distributed infrastructure, and parsing engines. OloStep also maintains low latency, typically between two and six seconds, by combining optimized infrastructure and logic.</p><p>Additionally, OloStep enables agents to reason about which pages to crawl, which subpages to explore, and how to filter results. Agents using OloStep do not need to manage the plumbing of proxies, anti-blocking measures, or parser maintenance. The agent issues high-level commands, and OloStep handles extraction, cleaning, formatting, and delivery.</p><p>Because OloStep converts websites into structured outputs and enables agents to build higher-level workflows, it transforms the web into a real-time database usable by agents and applications. In effect, OloStep abstracts away the messy details, allowing agents to focus on reasoning and decision-making.</p><h4>Key Capabilities of Autonomous Scraping Agents</h4><p>Here are several capabilities that distinguish modern scraping agents:</p><ol><li><strong>Goal-driven workflows</strong>: Agents receive intents, such as “collect top 100 product listings for brand X,” and orchestrate multiple steps, including crawling, filtering, logging in, pagination, and error recovery.</li><li><strong>Dynamic adaptation</strong>: When a page fails, when elements shift, agents can detect anomalies, adjust selectors, or pivot strategies rather than fail outright.</li><li><strong>Self-improvement</strong>: Some agents can learn reusable “skills” (for example, how to navigate a site or extract tables) and refine their library over time. As agents accrue experience, they become faster and more accurate.</li><li><strong>Collaborative human-agent control</strong>: In edge cases, agents may defer to human review or allow human override, improving reliability and trust.</li><li><strong>Scalability across domains</strong>: Because agents reason rather than rely on hand-coded rules, they can scale across many websites, domains, languages, and content types.</li></ol><h4>Challenges and Ethical Considerations</h4><p>Despite their promise, these agents face real challenges:</p><ul><li>Sites fight back. Measures like CAPTCHA, bot detection systems, and active blocking make scraping harder.</li><li>Changes in site licensing, terms of service, or robot policies require agents to follow legal and ethical constraints.</li><li>Agent hallucination or misbehavior risk exists when agents misinterpret content or take unintended actions.</li><li>Infrastructure cost grows when agents operate at scale across many domains.</li><li>Maintaining agent safety, monitoring, auditability and human oversight becomes essential.</li></ul><p>Moreover, the arms race continues: as agents get smarter, websites will adopt more advanced defenses, including behavioral heuristics or dynamic content injection.</p><h4>The Future Landscape</h4><p>Looking ahead, autonomous agents will become a core layer of the web. The concept of a Web of Agents (where agents interact, collaborate, and request services from each other) is already being theorized in academia. In that world, web scraping capabilities will be internalized: agents will query agent services to fetch fresh data rather than building their own scrapers.</p><p>Hybrid systems will dominate. Agents will issue goals while robust scraping platforms like OloStep serve as the reliable data backbone. Agents will compose skills, reuse parsing modules, coordinate across domains, and even negotiate data contracts.</p><p>We will also see specialization. Some agents will focus on financial use cases, while others will concentrate on market monitoring, brand protection, compliance, or social listening. Each will lean on tailored scraping stacks behind the scenes.</p><p>As agents evolve, the boundary between crawling, searching, and reasoning will become increasingly blurred. The future is a world where agents not only read the web but also act on it.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a19f836ae803" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5 Best Tools to Turn Websites into AI-Ready Data]]></title>
            <link>https://medium.com/@davidfagb/5-best-tools-to-turn-websites-into-ai-ready-data-f6dad0f21b3e?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/f6dad0f21b3e</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[web-scrapers]]></category>
            <category><![CDATA[scrapping]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Sun, 12 Oct 2025 14:18:05 GMT</pubDate>
            <atom:updated>2025-10-12T14:18:05.795Z</atom:updated>
            <content:encoded><![CDATA[<p>Imagine you’re building an LLM app that requires clean, up-to-date web content, product specifications, blog posts, and policies. But every site is its own mess, the HTML, ads, scripts, and weird microcopy. You don’t want raw HTML. You want clean markdown or JSONL, metadata, and embeds ready for chunking, embedding, and pushing into a vector DB.</p><h4>How I picked these five</h4><p>Selection criteria you care about:</p><ol><li><strong>Data quality</strong> (HTML → Markdown / clean text / JSONL)</li><li><strong>LLM &amp; vector-DB friendliness</strong> (embeddings pipeline, LangChain integrations, Pinecone/Weaviate exports)</li><li><strong>Scalability &amp; real-time</strong> (APIs, crawl scheduling, proxies)</li><li><strong>Legal &amp; ethical compliance</strong> (terms, takedown handling, human review where relevant)</li><li><strong>Developer ergonomics</strong> (no-code UI, SDKs, output formats).</li></ol><p>Here are five tools that make that dream real, and why Olostep should be the first thing you try.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T8lZyRUdAKxmmwXdU-DMZw.png" /></figure><h4>Olostep — from URL to LLM-ready content, fast</h4><p>If your priority is getting <em>clean markdown</em> or structured JSON directly from a URL, Olostep feels built for that last-mile moment before an LLM. It advertises simple endpoints that convert pages into Markdown, structured fields, and enables you to produce JSONL for embeddings without a full cleaning pipeline. For teams who treat web pages as content to be read (not raw HTML to parse), Olostep removes steps and friction. (<a href="https://olostep.com/blog/turn-websites-into-llm-ready-data?utm_source=chatgpt.com">Olostep</a>)</p><p><strong>Why use it:</strong> Minimal transformation work, output formats that match LLM ingestion patterns, and a developer-first API that can slot directly into an RAG or vector DB pipeline.</p><h4>Spidra — no-code + LLM assistance for tricky pages</h4><p>Spidra takes a different angle: a no-code interface that simulates real browser actions (click, scroll, fill), with LLM-driven extraction helpers for odd layouts. It’s great when you need human-like interaction to reveal content (lazy-loaded sections, gated lists) and want to export structured records without writing a spider. If you prefer GUI builds and occasional manual tuning, Spidra reduces the ramp. (<a href="https://spidra.io/?utm_source=chatgpt.com">spidra.io</a>)</p><p><strong>Why use it:</strong> Fast for one-off or low-code pipelines where human-style navigation matters.</p><h4>Apify — the flexible platform for custom pipelines</h4><p>Apify is the go-to when you need control: custom actors, scheduled crawls, and a marketplace of scrapers. It’s built to scale and to integrate with downstream systems — you can generate structured outputs, run post-processing, and push results to storage or an LLM pipeline. If your project grows from POC to product, Apify gives the building blocks. (<a href="https://apify.com/?utm_source=chatgpt.com">Apify</a>)</p><p><strong>Why use it:</strong> Highly customizable, great for teams that expect to iterate on scraper logic and orchestration.</p><h4>Bright Data — enterprise-grade scale and proxy coverage</h4><p>When scale and reliability across multiple geo-targets matter, Bright Data’s integrated proxy and scraping offerings are hard to beat. They emphasize large proxy pools and managed scrapers, which help keep long-running crawls healthy and less likely to be blocked. For enterprise data collection to feed large LLMs, reliability matters. (<a href="https://brightdata.com/?utm_source=chatgpt.com">Bright Data</a>)</p><p><strong>Why use it:</strong> Best choice for high-volume, geo-targeted extraction where anti-block and uptime are critical.</p><h4>Zyte &amp; ecosystem tools — LLM-friendly best practices</h4><p>Zyte (and players in its orbit) have focused on integrating LLMs using AI-assisted selectors, QA, and RAG-style approaches for search-and-extract. If your scraping is research-heavy, tools from this space help with quality checks and smarter extraction logic — the kind that reduces garbage data entering your embeddings. (<a href="https://www.zyte.com/webinars/llms-in-web-scraping-a-2025-roundup-from-zytes-data-science-leaders/?utm_source=chatgpt.com">Zyte #1 Web Scraping Service</a>)</p><p><strong>Why use it:</strong> Strong on extraction quality, developer tooling, and best practices for LLM feeding.</p><h4>How to choose (short checklist)</h4><ol><li><strong>Output format:</strong> Need markdown or JSONL out-of-the-box? Prioritize Olostep or Apify.</li><li><strong>Interaction:</strong> Do you need clicks or form fills? Spidra or Apify (browser automation).</li><li><strong>Scale &amp; geo:</strong> Big volume + geotargeting → Bright Data.</li><li><strong>Quality &amp; QA:</strong> If you need smart extraction and LLM-assisted checks, look at Zyte and Apify.</li><li><strong>Compliance:</strong> Always respect robots.txt and site terms, rate-limit requests, and prefer ethically sourced proxies or managed platforms to reduce legal risk.</li></ol><h4>Why Olostep is the everyday choice for LLM builders</h4><p>If your goal is “feed an LLM or a vector DB right now,” Olostep shines because it <strong>reduces transformation work</strong>: fewer cleaning steps, native markdown/JSON outputs, and a direct API path into embeddings or RAG pipelines. For fast experiments or production flows that demand consistent, LLM-ready records, it’s the pragmatic first stop.</p><p>Try Olostep for the initial ingest, and add Apify or Bright Data later as your scale or interaction needs grow. (<a href="https://www.olostep.com/?utm_source=chatgpt.com">Olostep</a>)</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f6dad0f21b3e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Top 6 Web Scraping APIs for Developers in 2025]]></title>
            <link>https://medium.com/@davidfagb/top-6-web-scraping-apis-for-developers-in-2025-a0691b8f1b9f?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/a0691b8f1b9f</guid>
            <category><![CDATA[scraper]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[web-scrapers]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Sun, 12 Oct 2025 13:28:13 GMT</pubDate>
            <atom:updated>2025-10-12T13:28:13.246Z</atom:updated>
            <content:encoded><![CDATA[<p>In the early days of the web, developers wrote simple scripts to fetch pages and parse HTML. A few lines of Python, a requests call, and maybe a BeautifulSoup import, and you could harvest the world’s data like picking apples from an open orchard.</p><p>However, as websites grew more dynamic, JavaScript-heavy, and protected by anti-bot firewalls, the simple approach began to fall apart. The modern web doesn’t just <em>serve</em> data anymore; it <em>hides</em> it behind authentication, React components, and captcha walls.</p><p>That’s why, in 2025, developers no longer build scrapers from scratch. They reach for scraping APIs,<strong> </strong>pre-built engines that handle rendering, rotation, and reliability. These services are the unsung heroes of the AI era, quietly feeding large language models, research dashboards, and e-commerce trackers with fresh, structured web data.</p><figure><img alt="Top 6 Web Scraping APIs for Developers in 2025 cover picture" src="https://cdn-images-1.medium.com/max/1024/1*AIUWE74jFKZ05qfipCNRrA.png" /><figcaption>Top 6 Web Scraping APIs for Developers in 2025 cover picture</figcaption></figure><p>Let’s explore the Top 6 Web Scraping APIs shaping how developers extract, format, and scale web data today, and how each one compares on speed, formats, and developer experience.</p><h4><a href="https://apify.com/">Apify</a> — The Automation Workhorse</h4><p>If Olostep is elegance, Apify is endurance.</p><p><a href="https://apify.com/">Apify</a> has long been the backbone of web automation. Its “Actors” — prebuilt scraping scripts for popular sites — make it a dream for developers who want flexibility and scale. Need to crawl Amazon listings or extract real estate data? Someone’s already built that Actor.</p><p>Apify’s strength lies in community scripts, integrations, and workflow orchestration. You can connect crawlers to storage, queues, or even AI post-processors with minimal setup.</p><ul><li><strong>Speed &amp; Success Rate:</strong> Moderate to high, depending on the Actor setup.</li><li><strong>JS Handling:</strong> Uses headless browsers (Playwright, Puppeteer).</li><li><strong>Formats:</strong> JSON, CSV, HTML.</li><li><strong>Cost per 1,000 requests:</strong> Mid-range; pricing based on compute units.</li><li><strong>Developer Experience:</strong> Feature-rich, slightly steeper learning curve.</li></ul><p>Apify remains the veteran choice for those who need <strong>customization over simplicity</strong>.</p><h4><a href="http://Spidra.io">Spidra</a> — The AI-Enhanced Scraper</h4><p>If Olostep is precision, <a href="https://spidra.io/"><strong>Spidra</strong></a> is instinct. Spidra is an AI-enhanced web crawler that dynamically crawls sites, rather tha<strong>n </strong>relying on hard-coded selectors. It combines a crawling, extraction, and summarization workflow, enabling you to access all products on the site,” a more efficient approach.</p><p>Its machine-learning-powered “auto-extract” feature identifies meaningful sections (titles, prices, content blocks) automatically. Developers love its “crawl intent” model; you tell it <em>what</em> you want, and it determines the best way to obtain it.</p><ul><li><strong>Speed:</strong> 8.5/10 — adaptive crawling</li><li><strong>Formats:</strong> JSON, structured tables</li><li><strong>JS Handling:</strong> Yes, AI-controlled browser sessions</li><li><strong>Cost per 1,000 requests:</strong> Competitive mid-range</li><li><strong>Best for:</strong> AI-driven extraction, e-commerce, and dynamic sites</li></ul><figure><img alt="Spidra" src="https://cdn-images-1.medium.com/max/1024/1*AnpLfTer8PJCUczHnYgI-A.png" /><figcaption>Spidra</figcaption></figure><p>Spidra feels like the bridge between human intuition and automation — a scraper that thinks before it scrapes.</p><h4>ZenRows — The Developer’s Sweet Spot</h4><p>ZenRows balances flexibility and affordability. It handles JS rendering, proxy rotation, and retries, all behind a single API endpoint.<br>It’s ideal for developers who want predictable performance without the overhead of enterprise solutions.</p><ul><li><strong>Speed:</strong> 8.5/10</li><li><strong>Formats:</strong> HTML, JSON</li><li><strong>JS Handling:</strong> Yes</li><li><strong>Cost per 1,000 requests:</strong> Mid-range</li><li><strong>Best for:</strong> Mid-scale scraping operations</li></ul><p>ZenRows is the steady workhorse, not flashy, but dependable.</p><h4>Olostep — The Markdown-First and AI-Ready Scraper</h4><p>Imagine if you could turn <em>any</em> website into clean, AI-digestible text in seconds; that’s Olostep’s superpower.</p><p><a href="https://www.olostep.com/">Olostep</a> is a newcomer that feels like it was built <em>for</em> 2025. Instead of just spitting out HTML, it provides structured Markdown, clean JSON, and even rendered HTML formats that plug directly into AI pipelines and content automation tools.</p><p>Where others focus on “scraping,” Olostep focuses on <strong>data readiness</strong>. Its APIs are designed to feed LLMs, not just databases. Developers can send a single URL or upload a batch of thousands, and within minutes, receive fully parsed, JS-rendered results.</p><ul><li><strong>Speed &amp; Success Rate:</strong> Near-instant for static pages, and surprisingly quick for JavaScript-heavy sites.</li><li><strong>JS Handling:</strong> Full browser rendering with bot-evasion techniques.</li><li><strong>Formats:</strong> HTML, Markdown, JSON, PDF.</li><li><strong>Cost per 1,000 requests:</strong> Low — with generous batch pricing and free tier credits.</li><li><strong>Developer Experience:</strong> Lightweight REST API, clean docs, no SDK bloat.</li></ul><figure><img alt="OloStep playground" src="https://cdn-images-1.medium.com/max/1024/1*yCl5p2rxQbpFEFenIj8mkg.png" /><figcaption>OloStep playground</figcaption></figure><p>Olostep feels less like a scraping tool and more like a data pipeline engine,<strong> </strong>ready for anyone building AI agents, automation bots, or analytical dashboards.</p><h4>ScraperAPI — Simplicity That Scales</h4><p>ScraperAPI’s appeal is its plug-and-play simplicity. You make an API call, it returns a fully rendered page, no proxy management, no rotating IP headaches.<br>Perfect for teams that want something that “just works.”</p><ul><li><strong>Speed:</strong> 9/10</li><li><strong>Formats:</strong> HTML, JSON</li><li><strong>JS Handling:</strong> Yes</li><li><strong>Cost per 1,000 requests:</strong> Low to moderate</li><li><strong>Best for:</strong> Quick data pulls and prototypes</li></ul><p>ScraperAPI is the <strong>fast food</strong> of scraping — simple, reliable, and always open.</p><h3>Bright Data — The Enterprise Giant</h3><p>Bright Data operates on a different scale, think billions of requests per day across global proxy networks.<br>It’s built for enterprises that can’t afford downtime and need access to the full spectrum of the web.</p><p>It offers managed scraping, proxy rotation, data labeling, and even AI model integration. But it comes at a premium price and complexity level.</p><ul><li><strong>Speed:</strong> 10/10</li><li><strong>Formats:</strong> JSON, CSV</li><li><strong>JS Handling:</strong> Industry-leading</li><li><strong>Cost per 1,000 requests:</strong> High (enterprise-grade)</li><li><strong>Best for:</strong> Large organizations &amp; high-volume data feeds</li></ul><p>Bright Data is the <strong>aircraft carrier</strong> of the scraping world — powerful but not agile.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WDR7x43Hb3oKxmiOD7gd2g.png" /><figcaption>Comparison Table</figcaption></figure><h3>The Future Belongs to Intelligence, Not Just Access</h3><p>All these tools scrape the web, but only a few <em>understand</em> it; that’s the new line dividing old-school crawlers from AI-ready data platforms.</p><p><a href="https://www.olostep.com/"><strong>Olostep</strong></a> represents that new wave of scrapers built not just to fetch pages, but to <em>interpret</em> them, <em>structure </em>them, and <em>feed</em> them into intelligent systems.</p><p>So if 2023 was about scraping the web, 2025 is about teaching your scraper to think.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a0691b8f1b9f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Turning the Web Into a Real-Time Database with OloStep]]></title>
            <link>https://medium.com/@davidfagb/turning-the-web-into-a-real-time-database-with-olostep-f175e956a603?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/f175e956a603</guid>
            <category><![CDATA[web]]></category>
            <category><![CDATA[web-scraping]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Wed, 08 Oct 2025 13:29:32 GMT</pubDate>
            <atom:updated>2025-10-08T13:29:32.242Z</atom:updated>
            <content:encoded><![CDATA[<p>You’re building an AI assistant for a startup CEO who needs to monitor competitors, track pricing updates, new hires, and product launches. Straightforward stuff. So, you start hunting for data scraping solutions that are too brittle to break after a minor site change. Most sites do not offer APIs. Google? Buried in ads, captchas, and unstructured results. You hit a wall.</p><p>Then it hits you that the data is all out there, it’s just trapped inside messy webpages. That’s precisely what OloStep is built to fix. Instead of scraping, crawling, or building static indexes, OloStep flips the model. It treats the open web itself as a live, structured, and queryable database in real time.</p><figure><img alt="Illustration of a chaotic web full of HTML pages turning into a clean, structured database view" src="https://cdn-images-1.medium.com/max/1024/1*kCHsAMclE1H0wYXNZjqlIA.png" /></figure><h4>The Web Wasn’t Meant to Be Queried</h4><p>The internet is one of the most extensive and valuable datasets in history. However, it was not designed for structure, but it was created for publishing.</p><p>There are billions of pages, including blogs, product listings, pricing tables, and job boards, which are constantly being updated. Yet, querying this information in a traditional manner is nearly impossible. You can crawl, index, and scrape the web, but these methods are often brittle, slow, and quickly become outdated.</p><p>What if, instead, you could treat the open web as if it were your live backend?</p><h3>What is Olostep?</h3><p>Olostep is a powerful web scraping API that efficiently provides e data from any website. It meets the essential demands for fast, reliable, and cost-effective data acquisition, AI development and large-scale data aggregation. rtups, and established companies enhance their data-driven applications and automate workflows.</p><h4>The Core Idea: Query the Open Web Like a Database</h4><p>Think SQL, but the tables are web pages. Think APIs, but the endpoints are search queries. OloStep turns the open web into a real-time knowledge source structured, filterable, and directly useful for agents, workflows, and apps.</p><p>Let’s say you’re building an AI agent that finds pricing trends for B2B SaaS tools. Traditionally, you’d:</p><ul><li>Crawl pages</li><li>Parse them manually</li><li>Store them</li><li>Run queries later</li></ul><p>But with OloStep:</p><pre>SELECT price, plan_name, vendor<br>FROM web<br>WHERE category = &quot;CRM software&quot; AND updated_within = &quot;7 days&quot;</pre><p>It’s not a stretch. This is the direction we’re heading to obtain structured results directly from the public internet, without scraping or even relying on pipelines, just answers.</p><figure><img alt="Side-by-side comparison — Left: messy HTML page, Right: clean JSON-like table extracted live]." src="https://cdn-images-1.medium.com/max/1024/1*NL3okKxPHQ4mnYe52GbmvQ.png" /><figcaption>Side-by-side comparison — Left: messy HTML page, Right: clean JSON-like table extracted live].</figcaption></figure><h4>What Makes OloStep Different</h4><p>Most tools that extract data from the web fall into one of two buckets:</p><ol><li><strong>Scrapers</strong>: Fast but fragile. Break often, need constant maintenance.</li><li><strong>Knowledge graphs: </strong>Structured but stale. Require time-consuming ingestion pipelines.</li></ol><p>OloStep is different because it utilizes AI-native tools to extract structured data pages in real<strong>-</strong>time. The model understands both layout and meaning. It can:</p><ul><li>Recognize product specs on landing pages</li><li>Pull out job listings across multiple sites</li><li>Extract facts from articles or documentation</li></ul><p>It’s a web-native query engine. No scraping rules. No brittle XPath selectors. If you’re building anything that depends on real-world knowledge, you don’t want to be stuck waiting for someone else to scrape, ingest, and update it for you.</p><figure><img alt="Flowchart showing an AI agent querying OloStep -&gt; real-time web pages -&gt; structured results" src="https://cdn-images-1.medium.com/max/1024/1*jl9B2mmsvoodZF5HbGzcoA.png" /><figcaption>Flowchart showing an AI agent querying OloStep -&gt; real-time web pages -&gt; structured results</figcaption></figure><h4>Why Now? Because the Stack Just Clicked</h4><p>This wasn’t possible five years ago.</p><ul><li><strong>LLMs</strong> were too weak.</li><li><strong>Scrapers</strong> were too brittle.</li><li><strong>Knowledge graphs</strong> were too slow.</li></ul><p>However, now LLMs can comprehend, reason over tables, and infer meaning across web pages in real-time.</p><p>OloStep rides that wave, turning raw web content into structured, semantically rich data at the moment you need it. It’s not just search. It’s understanding<strong>.</strong></p><h3>Use Cases Already Emerging</h3><p>Below are the benefits of implementing OloStep:</p><ul><li>AI Research Agents: agents that answer questions by citing live data from across the web, not just summaries from cached pages.</li><li>Competitive Intelligence: pull pricing changes, product launches, or staffing shifts from public pages and career portals.</li><li>Custom Tools &amp; Dashboards: develop internal and public-facing information, such as public tenders, vendor updates, or compliance announcements.</li><li>Programmatic Search: replace brittle Google scraping with structured queries and filters. No hacks. No captchas. Just answers.</li></ul><h4>The Future: Don’t Store the Web. Ask It.</h4><p>OloStep flips the traditional methods so instead of trying to tame the chaos of the internet into a static database, it embraces the mess and makes it readable, understandable, and usable.</p><p>Think of it like this:</p><ul><li>Search gives you links.</li><li>Scraping gives you fragments.</li><li>OloStep provides you with facts, and it does so in real-time.</li></ul><p>So instead of building brittle scraping tools or waiting for someone to publish a dataset, you can now ask the web a question and get structured, filtered, useful data in return.</p><p>The web is no longer just something you read; it’s something you query.</p><h4>Try It Yourself</h4><p>If you’re building agents, dashboards, or any product that depends on fresh, structured information from the real world, OloStep gives you the edge.</p><p>Check it out at <a href="https://olostep.com/">https://olostep.com</a><br>Start asking the web real questions and get real answers.</p><p>With OloStep, you don’t have to.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f175e956a603" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real-Time User Login Alerts system with Twilio + Next.js]]></title>
            <link>https://medium.com/@davidfagb/real-time-user-login-alerts-system-d69-2d691b1cd08b?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/2d691b1cd08b</guid>
            <category><![CDATA[login]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[airdrops-alert]]></category>
            <category><![CDATA[strapi]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Fri, 26 Sep 2025 15:41:16 GMT</pubDate>
            <atom:updated>2025-09-26T16:11:40.324Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rxN9e4iKXhu_wysaKoTIuA.png" /><figcaption>Cover image for: Real-Time User Login Alerts system with Twilio + Next.js</figcaption></figure><p>Sometimes, you want to know immediately when a user logs into your app, whether it’s for security reasons, user activity monitoring, or simply keeping track of engagement. Real-time login alerts can help you detect unauthorized access attempts or provide useful analytics.</p><p>In this tutorial, we’ll show you how to send real-time SMS alerts using Twilio whenever a user signs in. You’ll build this system using [Next.js](<a href="https://nextjs.org/docs">https://nextjs.org/docs</a>), [Strapi](<a href="https://strapi.io">https://strapi.io</a>), and [Twilio](<a href="https://console.twilio.com/">https://console.twilio.com/</a>), and you can test it locally using ngrok.</p><p>* Next.js will serve as the frontend framework, offering a fast, modern, and highly customizable interface for your app’s login screen. It supports API routes out of the box, which simplifies handling backend logic in a unified codebase.</p><p>* Strapi, a powerful open-source headless CMS, will manage user authentication and act as the backend. It provides a ready-to-use user model and robust authentication system with minimal setup, saving you time and letting you focus on core functionality.</p><p>* Twilio will handle the real-time SMS delivery. Once a user logs in, your application will use Twilio’s programmable messaging API to send a notification that includes the user’s email, IP address, and login timestamp.</p><h4>Prerequisites</h4><p>- <a href="https://nodejs.org/en/learn/getting-started/introduction-to-nodejs">Node.js</a> and npm installed<br>- Basic <a href="https://nextjs.org/docs">Next.js</a> app set up<br>- <a href="https://console.twilio.com/">Twilio</a> account with verified phone number<br>- <a href="https://ngrok.com/)">Ngrok</a> installed (for optional webhook testing)<br>- Strapi instance running (locally or hosted)<br>- <a href="https://www.twilio.com/docs/sendgrid/ui/account-and-settings/api-keys">SendGrid</a>: for sending the emails</p><h4>Creating a new Next.js project</h4><p>To get started with the article, let’s create a new Next.js project using the command below.</p><pre><br>npx create-next-app@latest<br>```</pre><p>Running the application above will prompt you to configure the project. Then configure it as shown below.</p><pre><br>What is your project named? my-app<br>Would you like to use TypeScript? No / Yes<br>Would you like to use ESLint? No / Yes<br>Would you like to use Tailwind CSS? No / Yes<br>Would you like your code inside a `src/` directory? No / Yes<br>Would you like to use App Router? (recommended) No / Yes<br>Would you like to use Turbopack for `next dev`? No / Yes<br>Would you like to customize the import alias (`@/*` by default)? No / Yes<br>What import alias would you like configured? @/*<br>```</pre><h4>Creating the project structure</h4><p>The project structure includes a `.env` file at the root, which will be used to store Twilio credentials securely. Inside the pages directory, there’s an api folder containing notify-login.js, which serves as the backend API route to send SMS alerts using the Twilio SDK. This function reads credentials from the environment and sends a login notification to a specified phone number. <br>The `test-login.js` page provides a simple frontend with a button that triggers this API, simulating a user login and prompting an SMS alert. The `index.js` file serves as the homepage, linking to the test login page.</p><p>This setup allows you to simulate and verify SMS alerts during user logins, laying the groundwork for integration with real authentication flows later. Running the app with. and visiting `/test-login` triggers the alert system, confirming server locally, up correctly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/688/0*SOAy1t3ehF1H0v_6.png" /></figure><h4>Setting up Twilio</h4><p>Next, log in to your <a href="https://console.twilio.com/">Twilio Console</a> to retrieve your Account SID, Auth Token, and Twilio phone number, as you can see in the screenshot below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6jBYeEVdhLqlxy20.png" /></figure><p>Then, use them to replace `&lt;your_twilio_account_sid&gt;`, `&lt;your_twilio_auth_token&gt;`, and `&lt;your_twilio_phone_number&gt;`, respectively, in `.env`.<br>This information is necessary because it authenticates your application with Twilio’s API, allowing it to send and receive SMS messages securely. The Twilio phone number serves as your application’s sender ID, allowing with real mobile eive message status updates viawebhooks.</p><h4>Create the environment variables</h4><p>To keep your API keys and other important information safe, use environment variables in a `.env file`. Start by creating a .env file in your your Strapi folder and adding the keys you need there.</p><pre>APP_KEYS=key1,key2,key3,key4<br>API_TOKEN_SALT=replace_me<br>ADMIN_JWT_SECRET=replace_me<br>JWT_SECRET=replace_me<br>EMAIL_SMTP_HOST=smtp.sendgrid.net<br>EMAIL_SMTP_PORT=587<br>EMAIL_SMTP_SECURE=false<br>EMAIL_SMTP_USER=apikey<br>EMAIL_SMTP_PASS=your_sendgrid_api_key<br>EMAIL_DEFAULT_FROM=alerts@yourdomain.com<br>EMAIL_DEFAULT_REPLY_TO=alerts@yourdomain.com<br>ALERT_RECIPIENT_EMAIL=alerts.receiver@example.com<br>TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br>TWILIO_AUTH_TOKEN=your_twilio_auth_token<br>TWILIO_FROM=+1234567890<br>ALERT_RECIPIENT_PHONE=+234XXXXXXXXXX<br>HOST=0.0.0.0<br>PORT=1337<br><br></pre><p>These environment variables help your app work with Twilio and Strapi. `TWILIO_ACCOUNT_SID` and `TWILIO_AUTH_TOKEN` are similar to your Twilio username and password; they enable your app to send messages through Twilio. `TWILIO_PHONE_NUMBER` is the Twilio number that sends the SMS, and `ALERT_RECIPIENT_NUMBER` is the phone number that will receive the login alert. `NEXT_PUBLIC_STRAPI_URL` is the web address of your Strapi backend, which your Next.js app uses to connect to Strapi and check login details.</p><h4>Installing Twilio SDK</h4><p>To enable the application to send SMS to users, we need to install the Twilio <a href="https://www.twilio.com/docs/libraries">SDK</a> into the project. To do that, run the command below.</p><pre>npm install Twilio</pre><h4>Setting up Strapi to Send Email Alerts</h4><p>To complement your SMS alerts, you’ll also send email notifications using Strapi. This is useful for backup notifications or sending alerts to multiple recipients.</p><h4>Strapi Installation and Setup</h4><p>Create a folder where both your Strapi and Next.js app will reside. Give it any name. Now navigate to the folder and run the command below in your terminal. Ensure you are in the directory where you want to keep both your Strapi backend and Next.js frontend.</p><pre>npx create-strapi@latest</pre><p>The command above will display the details below:</p><pre>? What is the name of your project? strapi-backend<br>…<br>? Please log in or sign up. Skip<br>? Do you want to use the default database (sqlite) ? Yes<br>? Start with an example structure &amp; data? No<br>? Start with Typescript? Yes<br>? Install dependencies with npm? Yes<br>? Initialize a git repository? No</pre><p>Once you have successfully installed Strapi, cd into your strapi project and run the command below.</p><pre><br>npm run develop</pre><p>This should start up your Strapi application in the URL: <a href="http://localhost:1337/admin">http://localhost:1337/admin</a>. Follow the instructions on the screen to continue.</p><p>This is what your Strapi dashboard should look like after registering the new admin:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fU6k6gjQyIJsfax_.png" /></figure><h4>Enable the Email Plugin in Strapi</h4><p>Here, we are going to install the Strapi <a href="https://docs.strapi.io/cms/features/email?_gl=1*1gh6qf0*_gcl_au*MjIxMTAxNzA2LjE3NTM3MDMzNTE">email plugin</a>, run the command below in your terminal:</p><pre>npm install @strapi/plugin-email</pre><h4>Setting up Strapi Email plugin with SendGrid</h4><p>First, you need the email plugin and the SendGrid provider for Strapi.<br>In your Strapi project directory, run:</p><pre>npm install @strapi/plugin-email @strapi/provider-email-sendgrid</pre><p>Or if you use yarn, run:</p><pre>yarn add @strapi/plugin-email @strapi/provider-email-sendgrid</pre><p>For the sake of time and keeping the tutorial simple, click here for a full step-by-step guide to set up Strapi email plugin with SendGrid on the Strapi <a href="https://strapi.io/blog/strapi-email-and-password-authentication-with-nextjs-15-part-1)">blog</a>.</p><h4>Creating the API route to send SMS</h4><p>Now let’s create the part of your frontend project (built with Next.js) that sends the SMS. We’ll do this by adding a file called `notify-login.js` inside the `pages/api` folder.</p><p>This file will receive login details (like the user’s email, IP address, and time of login) through a `POST` request, and then use Twilio to send a text message with that info.</p><p>To do this, open your **Next.js project folder**, go to the `pages/api` folder, and create a new file named `notify-login.js`. This file will handle sending the SMS alert whenever a user logs in.</p><pre>// pages/api/notify-login.js<br>import twilio from &#39;twilio&#39;;<br>const client = twilio(<br> process.env.TWILIO_ACCOUNT_SID,<br> process.env.TWILIO_AUTH_TOKEN<br>);<br>export default async function handler(req, res) {<br> if (req.method !== &#39;POST&#39;) {<br> return res.status(405).json({ message: &#39;Method Not Allowed&#39; });<br> }<br>const { email, ip } = req.body;<br>if (!email || !ip) {<br> return res.status(400).json({ message: &#39;Missing email or IP address.&#39; });<br> }<br>try {<br> await client.messages.create({<br> body: `🔐 Login Alert: ${email} logged in from IP ${ip}`,<br> from: process.env.TWILIO_PHONE_NUMBER,<br> to: process.env.ALERT_RECIPIENT_NUMBER,<br> });<br>res.status(200).json({ message: &#39;Alert sent!&#39; });<br> } catch (error) {<br> console.error(&#39;Twilio Error:&#39;, error);<br> res.status(500).json({ message: &#39;Failed to send alert.&#39; });<br> }<br>}</pre><p>The code above sends a login alert via Twilio SMS when a POST request with `email` and `ip` is received. It uses environment variables for Twilio credentials and sends the alert to the user’s phone number, handling errors and invalid requests appropriately.</p><h4>The Frontend design</h4><p>Next, you will create `test-login.js` Page to simulate a login action by calling the API.</p><p>Copy the code below and paste it into your *test-login.js* file:</p><pre>// pages/api/notify-login.js<br>import twilio from &#39;twilio&#39;;<br>const client = twilio(<br> process.env.TWILIO_ACCOUNT_SID,<br> process.env.TWILIO_AUTH_TOKEN<br>);<br>export default async function handler(req, res) {<br> if (req.method !== &#39;POST&#39;) {<br> return res.status(405).json({ message: &#39;Method Not Allowed&#39; });<br> }<br>const { email, ip } = req.body;<br> if (!email || !ip) {<br> return res.status(400).json({ message: &#39;Missing email or IP address.&#39; });<br> }<br>try {<br> // Step 1: Send SMS via Twilio<br> await client.messages.create({<br> body: `🔐 Login Alert: ${email} logged in from IP ${ip}`,<br> from: process.env.TWILIO_PHONE_NUMBER,<br> to: process.env.ALERT_RECIPIENT_NUMBER,<br> });<br>// Step 2: Send Email via Strapi<br> await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/notify-login-email`, {<br> method: &#39;POST&#39;,<br> headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },<br> body: JSON.stringify({ email, ip }),<br> });<br>res.status(200).json({ message: &#39;Alert sent via SMS and Email!&#39; });<br> } catch (error) {<br> console.error(&#39;Alert Error:&#39;, error);<br> res.status(500).json({ message: &#39;Failed to send alert.&#39; });<br> }<br>}</pre><p>The code above defines a simple Next.js page that simulates a user login and triggers an SMS alert using the `/api/notify-login` endpoint. It includes a form where users can enter their email, fetches the user’s public IP using the <a href="https://api.ipify.org)">api-pify</a>, and sends both the email and IP to the backend API via a POST request.</p><p>The next step is the main Page, known as the <strong>index.js</strong> page, where a placeholder or real login page code would go. You can copy the code below and paste it into your <strong>index.js</strong> file:</p><pre>// pages/index.js<br>import { useState } from &#39;react&#39;;<br>import axios from &#39;axios&#39;;<br>export default function Home() {<br> const [email, setEmail] = useState(&#39;&#39;);<br> const [status, setStatus] = useState(&#39;&#39;);<br> const [loading, setLoading] = useState(false);<br>const getUserIP = async () =&gt; {<br> try {<br> const res = await axios.get(&#39;https://api.ipify.org?format=json&#39;);<br> return res.data.ip;<br> } catch (error) {<br> console.error(&#39;IP Fetch Error:&#39;, error);<br> return &#39;Unknown IP&#39;;<br> }<br> };<br>const handleSubmit = async (e) =&gt; {<br> e.preventDefault();<br> setStatus(&#39;&#39;);<br> setLoading(true);<br>const ip = await getUserIP();<br>try {<br> const res = await axios.post(&#39;/api/notify-login&#39;, { email, ip });<br> setStatus(res.data.message);<br> } catch (err) {<br> setStatus(&#39;Error sending alert.&#39;);<br> }<br>setLoading(false);<br> };<br>return (<br> &lt;main style={{ maxWidth: &#39;400px&#39;, margin: &#39;100px auto&#39;, textAlign: &#39;center&#39; }}&gt;<br> &lt;h1&gt; Login Portal&lt;/h1&gt;<br> &lt;form onSubmit={handleSubmit} style={{ display: &#39;flex&#39;, flexDirection: &#39;column&#39;, gap: &#39;10px&#39;}}&gt;<br> &lt;input<br> type= &quot;email&quot;<br> placeholder= &quot;Enter your email&quot;<br> value={email}<br> onChange={(e) =&gt; setEmail(e.target.value)}<br> required<br> style={{ padding: &#39;10px&#39; }}<br> /&gt;<br> &lt;button type=&quot;submit&quot; disabled={loading} style={{ padding: &#39;10px&#39; }}&gt;<br> {loading ? &#39;Logging in…&#39;: &#39;Login&#39;}<br> &lt;/button&gt;<br> &lt;/form&gt;<br> {status &amp;&amp; &lt;p style={{ marginTop: &#39;20px&#39; }}&gt;{status}&lt;/p&gt;}<br> &lt;/main&gt;<br> );<br>}</pre><p>The code above defines a simple login interface in a Next.js page that collects a user’s email, fetches their public IP address using the <strong>ipify</strong> API, and sends this data to the <strong>/api/notify-login</strong> endpoint via a POST request using <a href="https://axios-http.com/docs/intro">Axios</a>. It includes basic form handling with loading and status states, updating the UI to reflect success or error messages after submission. The UI consists of a centered form with an email input and a submit button, styled inline for simplicity.</p><h4>The External css</h4><p>Next, go to your public folder and create a file named style.css, then copy the code below and paste it into the <strong><em>style.css</em></strong> file.</p><pre>/* public/styles.css */<br>body {<br> font-family: Arial, sans-serif;<br> background: #f8f8f8;<br> margin: 0;<br> padding: 40px;<br>}<br>.container {<br> max-width: 400px;<br> margin: auto;<br> background: white;<br> padding: 30px;<br> border-radius: 8px;<br> box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);<br>}<br>input[type=&quot;email&quot;] {<br> width: 100%;<br> padding: 12px;<br> margin-bottom: 15px;<br> border-radius: 4px;<br> border: 1px solid #ccc;<br>}<br>button {<br> width: 100%;<br> padding: 12px;<br> background-color: #0070f3;<br> color: white;<br> border: none;<br> border-radius: 4px;<br> font-weight: bold;<br> cursor: pointer;<br>}<br>button:hover {<br> background-color: #005bb5;<br>}<br>.status {<br> margin-top: 20px;<br> font-weight: bold;<br>}</pre><p>The CSS code above styles a clean, centered login form with modern input fields, a bold submit button, and a status message section.</p><h4>Testing the application</h4><p>The next step is to run the simple command below in your terminal to start the application:</p><pre>npm run dev</pre><p>After running the command above, you should see something like the image below displayed in your terminal.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/930/0*MWKW1sJ0rRCknGjG.png" /></figure><h4>Ngrok setup</h4><p>Your Next.js app runs at <a href="http://localhost:3000">http://localhost:3000</a>, but Twilio can’t access that because it’s on your local machine. ngrok gives you a public URL that tunnels to your local server. This is essential for testing Twilio SMS alerts in real time.</p><p>In a new terminal tab, run the command below.</p><pre>ngrok http 3000</pre><p>This URL is now a secure tunnel to your local server, you’ll get output as shown in the screenshot below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DoxjmH4JRIBwOW0F.png" /></figure><h4>Add ngrok URL to Twilio Webhook</h4><p>Once ngrok is running and you’ve obtained a public HTTPS URL (e.g., <a href="https://abcd1234.ngrok.io">https://abcd1234.ngrok.io</a>), you’ll need to link it to your Twilio number so that incoming messages or events from Twilio are forwarded to your local development server.</p><p>To link your ngrok tunnel to a Twilio SMS webhook, start by visiting the <a href="https://www.twilio.com/console/phone-numbers/incoming">Twilio Console</a> and navigating to the <strong>Phone Numbers </strong>section. Click on the phone number you want to configure. Once you’re on the settings page for that number, scroll down to the <strong>Messaging</strong> section. Look for the field labeled <strong>A Message Comes In</strong>. This is where you provide the URL that Twilio will call when a new message is received.</p><p>In that field, paste your ngrok URL pointing to your local API route, for example: `<a href="https://abcd1234.ngrok.io/api/notify-login`">https://abcd1234.ngrok.io/api/notify-login`</a>. Be sure to replace `abcd1234` with the actual subdomain that ngrok assigned to your session. Next, select <strong>HTTP POST</strong> as the request method. Once done, scroll down and click <strong>Save</strong> to apply the changes. Twilio will now send incoming SMS data to your local server via the ngrok tunnel.</p><p>Image description can be seen below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*i2jg7q3xVnKeJqTX.png" /></figure><p>Next, visit <a href="http://localhost:3000/">http://localhost:3000/</a>. Then, type in your email address and click on the login button. The localhost page should be displayed as shown in the image below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XgHBN_e9c_opzrjZ.png" /></figure><p>After you click on the login button, an sms with your information, which includes your IP address and email address, will be sent to your Twilio-registered number, just as shown in the screenshots below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*85-7cQmkcupqbi1-.jpg" /></figure><p>The screenshot above shows the sms alert being received from Twilio with the login information.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YwPY457M7febXFnH.jpg" /></figure><p>The image above shows that the login details were received in email from Strapi.</p><h4>That is How to Build Real-Time User Login Alerts system with Twilio + Next.js</h4><p>You’ve now built a complete real-time login alert system using Twilio and Next.js, all from scratch. You created a custom API route to send SMS notifications, designed a test login page, and used an external CSS file for styling. With ngrok, you expose your local server to the internet, allowing Twilio to send requests directly to your API. You also learned how to link your ngrok URL to a Twilio webhook for real-time testing. With everything connected and working, you now have a solid foundation for monitoring user activity and expanding into full authentication systems. You can find the complete source code and setup instructions on my [GitHub repository](<a href="https://github.com/Davidfagb/-User-Login-Alerts-system-with-Twilio-Next.js-and-Strapi">https://github.com/Davidfagb/-User-Login-Alerts-system-with-Twilio-Next.js-and-Strapi</a>).</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2d691b1cd08b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Case for OAuth]]></title>
            <link>https://medium.com/@davidfagb/the-case-for-oauth-bf015601967d?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/bf015601967d</guid>
            <category><![CDATA[modern]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[the-case-for-oauth]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[oauth]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Tue, 26 Aug 2025 14:51:59 GMT</pubDate>
            <atom:updated>2025-08-26T14:51:59.391Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="The tutorial cover picture" src="https://cdn-images-1.medium.com/max/1024/1*2bEPlwF9oAViMli0yMM0Pw.png" /></figure><p>As developers and teams build more complex applications, one of the toughest parts they deal with is managing access and permissions. With more use of cloud services, APIs, and mobile apps, ensuring only authorized personnel can access these systems has become a big challenge.</p><p>The need for secure and scalable solutions is growing, and users expect a smooth experience when logging into apps, while companies want to protect sensitive data and avoid security risks. This has led to a demand for better ways to handle authentication and authorization, especially as systems get more distributed and user access becomes more varied.</p><h4>The Traditional Approach to Authentication</h4><p>Authentication is the process of verifying a user’s identity before allowing them access to an application. In the past, developers used a few standard methods to handle authentication.</p><p>Let’s look at three of the traditional approaches, which are Basic Authentication, Session-based Authentication, and Cookie-based<strong>.</strong></p><p><strong>Basic Authentication</strong></p><p>In this method, when the user is ready to perform an action that requires the server, they send their username and password every time. The server will receive the credentials, username, and password, check them, and if they are good, will allow the user access. This method has clear and significant security issues. The credentials are sent as plain text, and unauthorized parties can intercept the credentials with a basic sniffer, allowing them access to the account and perpetrating fraudulent activity with little effort.</p><p>In addition to the security vulnerabilities, this method does not scale for larger systems and provides a terrible user experience. Requiring the user to log in repeatedly is annoying and is an unnecessary barrier to authentication.</p><p><strong>Session-based Authentication</strong></p><p>When the user logs in, the system creates a session. The server stores the user’s session information and sends the session ID to the user’s browser.. Each request uses this ID to verify the user. While it’s more secure than Basic Authentication, it can put a strain on server resources as more users join, and it’s still vulnerable to session hijacking if a malicious actor steals the session ID. Additionally, sessions eventually expire, forcing users to log in again, which can be frustrating.</p><p><strong>Cookie-based</strong></p><p>This is similar to session-based, but instead of storing session data on the server, it stores a session ID or token in a cookie on the user’s browser. The browser sends the cookie with each authentication request. While it can reduce server load, it’s still vulnerable to attacks like cross-site scripting (<a href="https://en.wikipedia.org/wiki/Cross-site_scripting">XSS)</a>, where a malicious script steals the cookie. Cookies also expire or can be deleted, causing users to log in again unexpectedly.</p><h4>OAuth: The Evolution of Authentication</h4><p><a href="https://oauth.net/2/">OAuth </a>is a solution to the problems of managing and securing user authentication. It is a protocol that allows users to grant applications access to their data without sharing their passwords. Instead of directly handing over sensitive login credentials, OAuth uses tokens to enable secure access.</p><p>Here’s a quick look at how OAuth works:</p><ul><li><strong>Authorization Code Flow</strong>: This is the most common flow for web applications. The user logs in to an identity provider like <a href="https://developers.google.com/identity/protocols/oauth2">Google</a>, and the application gets an authorization code. This code is then exchanged for a token, which the app uses to access the user’s data.</li><li><strong>Implicit Flow</strong>: This flow is used for client-side applications, like mobile apps or single-page web apps. The user logs in, and instead of a code, the application directly receives a token. This flow is faster but less secure than the Authorization Code Flow.</li><li><strong>Client Credentials Flow</strong>: This flow is used when applications need to access their own data, not on behalf of a user. The application itself authenticates using its client ID and secret, and gets a token to access resources.</li></ul><h3>Why OAuth?</h3><p>OAuth simplifies the authentication process by replacing passwords with tokens, making it more secure and easier to scale across different services and devices.</p><p>Below are the reasons OAuth is a better alternative compared to the traditional methods:</p><ul><li><strong>Security</strong>: OAuth minimizes risk by keeping credentials private. Instead of sharing passwords, it uses tokens like <a href="https://www.jwt.io/">JWT</a> to authenticate users, making it scalable and secure.</li><li><strong>User Experience</strong>: OAuth makes logging in easier by allowing users to authenticate without passwords, such as using their <a href="http://mail.google.com/">Google</a> or <a href="http://facebook.com">Facebook</a> account.</li><li><strong>Scalability</strong>: OAuth supports scalable systems, especially for APIs and microservices. It ensures secure access even as systems grow and become more complex.</li></ul><h4>OAuth vs. Traditional Authentication</h4><p>Traditional authentication methods like session-based or cookie-based authentication often come with several drawbacks. OAuth, on the other hand, was created to address these issues, especially in scenarios where secure, scalable, and seamless user authentication is needed.</p><p>Below is a comparison between OAuth and traditional authentication methods, highlighting how OAuth solves everyday problems and the trade-offs involved.</p><figure><img alt="Difference between OAuth and Traditional authentication methods" src="https://cdn-images-1.medium.com/max/1024/1*wlKLh3A2RW66abH1KrALYQ.png" /></figure><h4>How OAuth Fits into the Modern Architecture</h4><p>OAuth plays a key role in modern architectures, particularly in microservices, by managing secure access between services without exposing sensitive credentials. It allows each service to request specific permissions, ensuring only authorized interactions.</p><p>For third-party API integrations, OAuth enables apps to securely access external services, like Google or Facebook, without needing users’ passwords.</p><p>In mobile app development, OAuth allows users to log in using trusted providers, such as Google or Apple, which enhances the user experience and eliminates the need for users to create new accounts. OAuth also works with Identity Providers (<a href="https://en.wikipedia.org/wiki/Identity_provider">IDPs</a>) to authenticate users and issue access tokens. OpenID Connect, an identity layer built on top of OAuth, adds functionality by providing user identity information, allowing apps to verify both the user’s identity and their access permissions.</p><h4>Limitations and Considerations</h4><p>While OAuth is a powerful solution, it does come with some challenges that developers need to consider, which include:</p><p><strong>Complexity for Small Teams</strong></p><p>Implementing OAuth can be complicated for smaller teams or solo developers. The setup often requires integrating with multiple services, configuring redirect URIs, and handling token management, which can be time-consuming and error-prone.</p><p><strong>Possible Challenges</strong></p><p>Token expiration means that tokens have a limited lifespan, requiring them to be refreshed periodically, which adds extra steps to the authentication process. Token theft can occur if tokens are not stored or transmitted securely, allowing attackers to steal and misuse them.</p><p>Additionally, OAuth has potential attack vectors like <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">Cross-Site Request Forgery</a> (CSRF) and replay attacks, which can happen if proper security measures are not in place.</p><p><strong>Learning Curve</strong></p><p>OAuth involves a steep learning curve, especially when managing different types of token access, refresh, ID tokens and dealing with various flows. Understanding and handling token expiration, revocation, and securing token storage can be overwhelming for developers new to OAuth.</p><h4>Conclusion</h4><p>Using OAuth can be complicated. It requires managing the duration of tokens and keeping them safe. Developers must also work with different OAuth methods, which can be tricky, especially for smaller teams or those new to OAuth. Even with these challenges, OAuth is still a popular choice for modern apps because it offers secure and scalable authentication.</p><p>To stay ahead in securing applications, developers and teams should adopt OAuth and keep up with its evolving capabilities.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bf015601967d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Top Python Frameworks For Game Development]]></title>
            <link>https://medium.com/@davidfagb/top-python-frameworks-for-game-development-d66500c06df5?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/d66500c06df5</guid>
            <category><![CDATA[game-design]]></category>
            <category><![CDATA[game-development]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[games]]></category>
            <category><![CDATA[python-programming]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Fri, 14 Mar 2025 08:51:52 GMT</pubDate>
            <atom:updated>2025-03-14T08:51:52.825Z</atom:updated>
            <content:encoded><![CDATA[<p>Hundreds of developers in the gaming industry happen to prefer Python as their go-to programming language. Popular games such as Battlefield 2, Pirates of the Caribbean, and many others were developed with Python natively.</p><p>Most developers prefer Python over other programming languages due to its functionality and add-ons. Thanks to advancements in the gaming development industry, Python programming is an excellent choice for rapid prototyping video games.</p><p>This article will teach you about the top Python frameworks for your next game development adventure.</p><h3>Python frameworks for game development</h3><p>Below are the top Python frameworks you need to know to hasten and help you in the production of your Python games:</p><h3>PyGame</h3><p>PyGame is a Python open-source library for creating applications and products, such as games.sing the excellent library created using C, Python, Native, and OpenGL. Pygame enables users to create games and multimedia apps. This library is portable and can operate on any operating system and platform.</p><h4>PyGame Basic Features:</h4><p>Below are features that make PyGame a sound library for game development</p><ul><li>This library readily accommodates multicore CPUs.</li><li>For key functionality, it employs efficient C and Assembles code.</li><li>This library is lightweight and portable.</li></ul><h3>PyKyra</h3><p>The PyKyra framework, which is used for game development, is the quickest Python framework. It is built on software and documentation localization (SDL) and functionalities. This Python language framework supports MPEG video and audio formats V, Ogg Vorbis, and many more.</p><h4>PyKyra Basic Features:</h4><p>Below are simple PyKyra features:</p><ul><li>Pykyra is a framework designed to handle both side symmetric and top-down translations.</li><li>In Pykyra, the user may divide the screen into sub-window views, each with its object transformation.</li><li>The Kyra engine is quick and competent, with specially designed algorithms for rendering updates.</li></ul><h3>PyGlet</h3><p>Pyglet is a robust Python library that serves as an open-source, cross-platform windowing and multimedia framework. This library is used for game creation and many other aesthetically pleasing apps and software for Windows, Linux, and Mac OS X. Pyglet is a Python language module that enables user interfaces for event handling, OpenGL graphics, joysticks, loading films and images, windowing, and playing sounds and music.</p><h4>PyGlet Features:</h4><p>Below are some incredible reasons to use the PyGlet library for your game development.</p><ul><li>It has native portable windowing.</li><li>Load practically any picture, sound, audio, or video format.</li><li>The Pyglet library does not require any installation and has no external dependencies.</li></ul><h3>PyOpenGL</h3><p>PyOpenGL is the most widely used cross-platform Python library. This framework connects Python to OpenGL and other APIs. The binding in the PyOpenGL framework was created by utilizing the standard c —types library. This is a Python-standardized framework that contains a large variety of external graphical user interfaces and audio libraries, such as PyGame, Raw Extended Library<strong>(</strong>XLib), Python binding for Qt<strong>(</strong> PyQt), and others.</p><h4>PyOpenGL Basic Features</h4><p>It has array handling, which includes:</p><ul><li>Integers (functioning as pointers-to-single-value arrays) (acting as pointers-to-single-value arrays).</li><li>arrays of ctypes</li><li>parameters for ctypes</li></ul><h3>Python Kivy</h3><p>The Kivy library is built with new user interfaces, such as multi-touch apps. The Kivy Library is used for quick application and software development. This framework is compatible with Windows, Android, Raspberry Pi, Linux, Mac OS X, and iOS. Many inputs, devices, and protocols, such as the Mac OS X Trackpad and Magic Mouse, WM Touch, WM Pen, Linux kernel HID, and TUIO, are automatically supported. Kivy is a graphics processing unit (GPU)-accelerated library. It also includes over 20 widgets, all of which are very expandable.</p><p>The Kivy library is free under an MIT license beginning with version 1.7.2 and LGPL 3 for older versions. This reliable framework comes with a well-documented API and a programming guide to help users get started.</p><h4>Kivy Features</h4><p>Kivy provides access to mobile APIs for manipulating things like a phone’s camera, GPS tracking, and vibrator, and it also includes numerous components for developing applications, such as:</p><ul><li>A graphics library based on OpenGL ES 2.</li><li>A wide variety of widgets that enable multi-touch.</li></ul><h3>Python-Ogre</h3><p>Python — Orge, sometimes known as PyOrge, is a game development framework. It is developed in C++ with Python bindings for the Orge 3D engine. This framework is cross-platform, with flexibility and speed. It offers an excellent feature set that is leveraged to create engaging games. The PyOrge framework comprises two libraries: Orge3D and Crazy Eddie Graphical User Interface (CEGUI) systems.</p><p>The Orge is used to design scenery and any graphics imported into the game; for versions built before Orge v1.05, the PyOrge framework is utilized. Following that, numerous additional modules become accessible.</p><h4>Python-Orge Basic Features</h4><ul><li>Graphics user interface library that is compact and lightweight.</li><li>A cross-platform audio API that is mainly used with the Ogre3D.</li><li>OGRE has an input library that supports buffering and object-oriented programming.</li></ul><h3>Soya3D</h3><p>Soya 3D is a Python high-level framework for creating 3D video games. This framework is free and licensed under the GNU General Public License ( GPL ). According to its framework, it is a multi-platform game creation engine that can operate on Linux, Microsoft Windows, and Oya 2Dis. It is entirely focused on speedy creation. It is pretty simple to use. The primary purpose of this framework is to enable novice users to create complex 3D games entirely in Python.</p><h4>Soya3D Basic Features</h4><ul><li>It supports sound rendering, input, and physical simulation.</li><li>All networking is done using basic Python APIs.</li><li>Soya has a comprehensive user guide detailing its design, functionality, and interfaces.</li></ul><h3>Conclusion</h3><p>Currently, gaming has become an active part of our everyday lives, and improvements in the gaming sector have driven developers to create high-quality, visually appealing games. In this article, you will see the top Python frameworks for game development and their distinct capabilities that are beneficial for generating various functions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d66500c06df5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Future of Workflow Orchestration: How Kestra is Leading the Way]]></title>
            <link>https://medium.com/@davidfagb/the-future-of-workflow-orchestration-how-kestra-is-leading-the-way-e95bd5e56d2c?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/e95bd5e56d2c</guid>
            <category><![CDATA[future]]></category>
            <category><![CDATA[workflow]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[orchestration]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Fri, 14 Mar 2025 08:41:00 GMT</pubDate>
            <atom:updated>2025-03-14T08:41:00.682Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/889/0*ZPZtnCsCBNgYSkcm" /></figure><p>This article explores how <a href="https://kestra.io/">Kestra</a> is not just keeping pace with the rapid changes in workflow orchestration but is leading the way. We’ll examine the key innovations that set Kestra apart from other tools, discuss how it addresses the current challenges in the field, and look ahead at the future trends in orchestration that Kestra is poised to influence.</p><p>By the end of this article, you’ll clearly understand why Kestra is positioned as a leader in the future of workflow orchestration.</p><h3>Overview of Workflow Orchestration</h3><p>Workflow orchestration has become a critical component in modern software development, enabling the seamless automation and management of complex processes across various systems. As businesses continue to scale and diversify their operations, the need for robust orchestration tools has never been greater. These tools ensure that tasks are executed in the correct order, dependencies are managed, and errors are handled efficiently, all while maintaining high levels of performance and reliability.</p><h3>Getting Started with Kestra</h3><p>Kestra is an innovative open-source orchestration tool designed to meet the evolving demands of today’s complex workflows. Unlike traditional tools that often struggle with scalability and real-time processing, Kestra offers a modern solution that is both powerful and user-friendly. With its ability to integrate with a wide range of technologies, Kestra provides a flexible platform for automating and orchestrating workflows at any scale. Whether you’re managing simple tasks or orchestrating intricate, multi-step processes, Kestra is engineered to handle the challenges of modern infrastructure.</p><h3>Kestra Installation</h3><p>Kestra is open source, which means anyone can run it on their machine for free. To set it up, ensure that you have Docker installed and running on your machine, then run the following command to start up your instance!”</p><p>```bash</p><p>docker run — pull=always — rm -it -p 8080:8080 — user=root \</p><p>-v /var/run/docker.sock:/var/run/docker.sock \</p><p>-v local server at /tmp:/tmp kestra/kestra:latest</p><p>```</p><p>Once you’ve run the above code, you can open your browser and navigate to <a href="https://localhost:8080">https://localhost:8080</a> to see the interface and begin developing workflows!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JuOzbXUT-V_X00Zo" /></figure><h3>Create Your First Flow</h3><p>Head to the Flows section on the left side, then click the ‘Create’ button. Copy the lines of code below and paste them to create your initial flow.</p><pre>id: myflow<br>namespace: company.team<br>tasks:<br>- id: hello<br>type: io.kestra.plugin.core.log.Log<br>message: Hello World! 🚀</pre><p>Make sure to click “Save” and then “Execute” to start your first execution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZxyntSPfSP7kfB4r" /></figure><h3>The Current Landscape of Workflow Orchestration</h3><p>The landscape of workflow orchestration has been shaped by several widely-used tools, each with its strengths and limitations. Apache Airflow, one of the most popular orchestration tools, offers a mature and extensible platform that has been adopted by many organizations for managing complex workflows. However, Airflow’s reliance on Python and its somewhat cumbersome setup can pose challenges for teams lacking deep expertise in programming or DevOps. Additionally, Airflow struggles with real-time processing, as its scheduling model is more suited to batch-oriented tasks.</p><p>These limitations highlight the need for more advanced solutions that can handle the growing complexity and scale of modern workflows being accessible to a broader range of users.</p><h3>Emerging Needs in Workflow Orchestration</h3><p>As organizations embrace digital transformation, the demand for sophisticated workflow orchestration tools has surged. Modern applications often require orchestrating workflows that span multiple systems, cloud environments, and data sources. These workflows must be flexible and scalable, capable of adapting to dynamic business needs while maintaining high levels of reliability and performance.</p><p>Real-time processing has also become increasingly important, particularly in finance, healthcare, and e-commerce industries, where timely data processing can be critical. Traditional batch-processing models are no longer sufficient in these contexts, driving the need for orchestration tools to handle real-time events and data streams.</p><p>These emerging needs underscore the importance of innovation in workflow orchestration. Tools that can meet these demands will be crucial in helping organizations maintain a competitive edge in an increasingly complex and fast-paced digital landscape, and that is where Kestra comes in to save the day.</p><h3>How Kestra Is Addressing Modern Orchestration Challenges</h3><p>Kestra is designed to overcome the limitations of traditional workflow orchestration tools by offering a suite of advanced features tailored to the demands of modern applications. One of Kestra’s standout capabilities is its scalability. Built with a distributed architecture, Kestra can quickly scale across multiple nodes, efficiently handling large, complex workflows. This scalability is crucial for enterprises managing workflows across vast datasets, numerous microservices, or geographically distributed systems.</p><p>Kestra also excels in real-time processing. Integration is another area where Kestra shines. It supports various plugins and connectors, allowing seamless integration with various data sources, cloud services, and third-party applications. This flexibility enables organizations to create workflows that interact with their entire tech stack, from databases and APIs to cloud storage and machine learning models.</p><h3>User-Friendly Design and Usability</h3><p>One of the barriers to adopting powerful orchestration tools is often their complexity. Kestra addresses this challenge by offering a user-friendly interface and a design that prioritizes ease of use. The platform is built with a declarative approach, where users can define workflows using a simple, YAML-based syntax. This reduces the need for deep programming knowledge, allowing a broader range of users, from data engineers to operations teams, to create and manage workflows efficiently.</p><h3>Open-Source Flexibility and Community Support</h3><p>As an open-source tool, Kestra offers flexibility that proprietary solutions often cannot match. Organizations can customize and extend Kestra to fit their needs by adding new features, integrating with custom tools, or optimizing performance for unique workloads. Kestra&#39;s open-source nature also means that it benefits from continuous improvement driven by a global community of developers and users.</p><h3>Future Trends in Workflow Orchestration</h3><p>One of the most significant trends shaping the future of workflow orchestration is the rise of low-code and no-code platforms. As businesses strive to become more agile, there is a growing demand for tools that allow users with little or no programming experience to create and manage complex workflows. Low-code/no-code orchestration platforms empower non-technical users to automate processes, reducing the reliance on specialized development teams and speeding up the time to market for new solutions.</p><p>Kestra is well-positioned to capitalize on this trend by potentially offering low-code/no-code features that complement its existing capabilities, such as providing a more visual, drag-and-drop interface or simplifying the workflow creation process.</p><h3>AI and Machine Learning in Orchestration</h3><p>Another emerging trend is integrating artificial intelligence (AI) and machine learning (ML) into workflow orchestration. AI and ML can enhance automation by enabling predictive analytics, intelligent decision-making, and error resolution. For instance, AI-driven orchestration tools could predict potential workflow bottlenecks and automatically adjust resource allocation to prevent delays. Machine learning models could analyze workflow patterns and suggest optimizations that improve efficiency and reduce costs.</p><p>Kestra has the potential to incorporate AI and ML capabilities to enhance its orchestration engine.</p><h3>Cloud-Native Orchestration</h3><p>As more organizations migrate their infrastructure to the cloud, the demand for cloud-native orchestration solutions grows. Cloud-native orchestration tools are designed to operate seamlessly in cloud environments, leveraging the cloud&#39;s elasticity, scalability, and capability of managing distributed systems, microservices, and applications while maintaining high performance and availability.</p><h3>Greater Focus on Security and Compliance</h3><p>With workflows becoming increasingly complex and the regulatory landscape growing, there is a heightened focus on security and compliance in workflow orchestration. Organizations need tools that automate processes and ensure that workflows comply with industry standards and regulations, such as GDPR, HIPAA, or SOC 2. Security features such as encryption, access controls, audit trails, and automated compliance checks are becoming essential.</p><h3>Enhanced Observability and Monitoring</h3><p>As workflows become more complex, enhanced observability and monitoring are critical. Organizations require detailed insights into their workflows&#39; performance and status, including metrics, logs, and real-time alerts. Enhanced observability allows teams to quickly identify and resolve issues, optimize workflows, and meet service level agreements (SLAs).</p><h3>Conclusion</h3><p>In summary, <a href="https://medium.com/@kestra-io/getting-started-with-kestra-as-a-non-technical-user-d201fc9dda36#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImIyZjgwYzYzNDYwMGVkMTMwNzIxMDFhOGI0MjIwNDQzNDMzZGIyODIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyMTYyOTYwMzU4MzQtazFrNnFlMDYwczJ0cDJhMmphbTRsamRjbXMwMHN0dGcuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTIzNzg0ODE0ODk1MTUyNzI5NjUiLCJlbWFpbCI6InRlbWl5ZTEzQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJuYmYiOjE3MjU0NDY3OTcsIm5hbWUiOiJUZW1peWVvbHV3YSBPZHVuYXlvIiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hL0FDZzhvY0pRUE1NN1V3QUtYRDZuejhKZlNCUkE4eEh0SXp3OUxTVjA5VmtEQ3dkd0FyMGJUUT1zOTYtYyIsImdpdmVuX25hbWUiOiJUZW1peWVvbHV3YSIsImZhbWlseV9uYW1lIjoiT2R1bmF5byIsImlhdCI6MTcyNTQ0NzA5NywiZXhwIjoxNzI1NDUwNjk3LCJqdGkiOiI0NGMxNmFjODQ1MmM4ZTk3ZWY5OTQ4MjZiNGU5Yzc1MWEyMjUzZDI3In0.YICcauFqUDO6j63wXW6DY50DFcM0BkI-vN6AF_2sow8Hpv47cmZyjiT_O7v2gU4p4z0BNo7n7q7nVlyn-TykGXVprtQ9Pc4CHlfMPlvE526RyrFxhOk43kkWwO7v8kVU6Gayd6vJcOqI12-n-wBvn9DKFjQz6bIPj6SWLa-GNuagoi2N_j8iiBEtGfDMOq64wwlMoWGc1ZwQbkNOnU-vbtCPfRLxjkkYG_on63mEMvEzzpfsTMD87MtKGugwjqCOw48jspA1h3pfDsydIjtAObbi632vFBcaQqz068euxLI1scpw6Bgn26wuv3zSLzKNDGSw-g6P9hs-JqJUTc71_w">Kestra</a> addresses the modern challenges of workflow orchestration by offering a highly scalable, real-time processing platform that is both powerful and accessible. Its user-friendly design, extensive integration capabilities, and the flexibility of open-source software make it an ideal choice for organizations looking to streamline their operations and stay ahead in a competitive landscape.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e95bd5e56d2c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Guide to Data Fetching in Next.js 14]]></title>
            <link>https://medium.com/@davidfagb/guide-to-data-fetching-in-next-js-14-997518eff0f4?source=rss-d1058d8a6759------2</link>
            <guid isPermaLink="false">https://medium.com/p/997518eff0f4</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[data]]></category>
            <category><![CDATA[nextjs-tutorial]]></category>
            <dc:creator><![CDATA[David Fagbuyiro]]></dc:creator>
            <pubDate>Sat, 18 Jan 2025 20:44:30 GMT</pubDate>
            <atom:updated>2025-01-18T20:54:13.242Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8jhw-nAX2kQjti_F.png" /></figure><p>Data retrieval is a crucial aspect of web development. It allows for the delivery of dynamic, real-time content, enhancing the user experience. It enables applications to obtain and display information quickly, seamlessly integrate with external services, and efficiently manage states without needing full page reloads.</p><p>This article details the evolution of data fetching methods in [Next.js](<a href="https://nextjs.org/docs">https://nextjs.org/docs</a>) from the early days to the present with version 14.</p><h4>Early Data Fetching Using getInitialProps</h4><p>Before Next.js version 9.3, [getInitialProps](<a href="https://nextjs.org/docs/pages/api-reference/functions/get-initial-props">https://nextjs.org/docs/pages/api-reference/functions/get-initial-props</a>) was the only way to retrieve server-side data. This function allows developers to collect data for a page before rendering; the server sends down a fully hydrated [HTML](<a href="https://developer.mozilla.org/en-US/docs/Web/HTML">https://developer.mozilla.org/en-US/docs/Web/HTML</a>) page to the client. This approach has a markedly positive impact on SEO and initial page load times.</p><p>The code below will create a simple blog page in Next.js 13 that fetches five blog posts from an external API and displays them. The `getPosts` function retrieves the posts, and the `HomePage` component renders the content with each post’s title and body in a list. The posts are fetched and rendered on the server using server-side components before being sent to the browser.</p><p>```javascript<br>async function getBreweries() {<br> const res = await fetch(‘<a href="https://api.openbrewerydb.org/breweries?per_page=4&#39;">https://api.openbrewerydb.org/breweries?per_page=4&#39;</a>);<br> if (!res.ok) {<br> throw new Error(‘Failed to fetch breweries’);<br> }<br> return res.json(); // Return the breweries array<br>}</p><p>const HomePage = async () =&gt; {<br> const breweries = await getBreweries();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’ }}&gt;<br> &lt;center&gt;&lt;h1 style={{ fontSize: ‘35px’, color: ‘#333’ }}&gt;Brewery Data Fetching Example&lt;/h1&gt;&lt;/center&gt;<br> &lt;ul&gt;<br> {breweries.map((brewery) =&gt; (<br> &lt;li key={brewery.id} style={{ marginBottom: ‘25px’ }}&gt;<br> &lt;h2 style={{ fontSize: ‘28px’, color: ‘#333’ }}&gt;{brewery.name}&lt;/h2&gt;<br> &lt;p style={{ fontSize: ‘22px’, color: ‘#555’ }}&gt;{brewery.city}, {brewery.state}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default HomePage;<br>```</p><p>The code above defines an `asynchronous` function, `getBreweries`, that fetches data from the open brewery API, specifically retrieving a list of four breweries. If the fetch request fails, it throws an error. Once the data is successfully fetched, the function returns the breweries as a `JSON` array.</p><p>The `HomePage` component is an asynchronous React component that calls the `getBreweries` function to fetch the data. It then renders a webpage with a yellow background (`#FFFF99`) and a minimum height of 100% of the viewport. Each brewery is displayed with its name, city, and state, styled with distinct font sizes and colors. The component maps through the breweries array and displays each brewery’s information inside list items. The layout emphasizes a clean, simple presentation of the fetched brewery data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*if4hOMIJqZHWldSo.png" /></figure><p>The data is fetched once before the page is rendered and sent to the client. The server fetches the data during the initial page load, and the client receives the fully rendered page with the data.</p><h4>Advancements with getServerSideProps and getStaticProps</h4><p>Next.js 9.3 added [getServerSideProps](<a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props">https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props</a>) and [getStaticProps](<a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props">https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props</a>), two key enhancements that improve data fetching capabilities and online application efficiency and flexibility. Other `getServerSideProps` and `getStaticProps` benefits include the following:</p><p>* `getServerSideProps` fetches data on the server for each request, making it suitable for dynamic content. Key advancements include improved performance through optimized server-side rendering, support for streaming HTML content, built-in caching to avoid unnecessary data fetching, and the ability to leverage Incremental Static Regeneration (ISR) for a hybrid approach.</p><p>* `getStaticProps` fetches data at build time, generating static pages for quick delivery. Enhancements include better Static Site Generation (SSG), revalidation options for automatic page regeneration, hybrid rendering capabilities to mix static and dynamic content, and improved developer experience with error handling and TypeScript support.</p><p>* Choose `getServerSideProps` for fresh data on every request and `getStaticProps` for infrequent updates and better performance. Combining these methods allows for flexible and optimized application development.</p><h4>Example of Fetching Data on Every Request</h4><p>Here’s a simple example of how `getServerSideProps` can retrieve data for each request.</p><p>The code below creates a simple blog page in Next.js where the `homepage` displays a list of blog posts, where the `homepage` component receives an array of posts and shows them with each post’s title and body. The `getServerSideProps` function fetches five posts from an external API (`<a href="https://jsonplaceholder.typicode.com/posts?_limit=3`">https://jsonplaceholder.typicode.com/posts?_limit=3`</a>) whenever a user requests the page. The posts are passed as props to the component. The expected output is a page showing three blog posts with titles and descriptions, dynamically fetched for each page load.</p><p>Successful Data Fetch on Fetching Data on Every Request with `getServerSideProps`</p><p>```javascript<br>const fetchPosts = async () =&gt; {<br> const res = await fetch(‘<a href="https://jsonplaceholder.typicode.com/posts?_limit=5&#39;">https://jsonplaceholder.typicode.com/posts?_limit=5&#39;</a>);<br> <br> // Check if the response is successful<br> if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>return res.json();<br>};</p><p>const HomePage = async () =&gt; {<br> try {<br> const posts = await fetchPosts();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Example of Fetching Data on Every Request (Success)&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ marginBottom: ‘20px’ }}&gt;<br> &lt;h2&gt;{post.title}&lt;/h2&gt;<br> &lt;p&gt;{post.body}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br> } catch (error) {<br> return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Error: {error.message}&lt;/h1&gt;<br> &lt;/div&gt;<br> );<br> }<br>};</p><p>export default HomePage;<br>```</p><p>In the above code example, `getServerSideProps` retrieves blog entries from an API with each request. This ensures that the data on the page is constantly current, which is especially useful for content that changes often or varies depending on the user’s context. Image output can be seen below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7o2YW-f33LtO6eU0.png" /></figure><p>Second example of Failed Data Fetch with `getServerSideProps`</p><p>This code demonstrates how to handle both successful and failed data fetching in Next.js using the App Router. Instead of `getServerSideProps`, the `fetch` API is used directly within the server component. On success, it displays blog posts; on failure, it catches the error and shows an error message.</p><p>```javascript<br>export async function getServerSideProps() {<br> try {<br> const res = await fetch(‘<a href="https://invalid-api-url.com/posts?_limit=5&#39;">https://invalid-api-url.com/posts?_limit=5&#39;</a>);<br> <br> // Check if the response is successful<br> if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>const posts = await res.json();</p><p>return {<br> props: { posts },<br> };<br> } catch (error) {<br> return {<br> props: { error: error.message },<br> };<br> }<br>}</p><p>const HomePage = ({ posts, error }) =&gt; {<br> if (error) {<br> return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Error: {error}&lt;/h1&gt;<br> &lt;/div&gt;<br> );<br> }</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Blog Posts&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ marginBottom: ‘20px’ }}&gt;<br> &lt;h2&gt;{post.title}&lt;/h2&gt;<br> &lt;p&gt;{post.body}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default HomePage;<br>```<br>This code above attempts to fetch data from an API. If the fetch is successful, the posts are displayed as a list. If the fetch fails due to an invalid URL, an error message is shown. The page has a yellow background and black text, with posts or error messages styled appropriately. The fetch operation is wrapped in a `try-catch` block to handle errors and display them when they occur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5Iszm5ZcLHeg__V-.png" /></figure><p>## Server-Side Rendering with getServerSideProps</p><p>The `getServerSideProps` function retrieves data from the server at the request time. This technique is appropriate for pages that need to display frequently updated information.</p><p>The code below will create a simple blog page in Next.js that fetches and displays the latest four blog posts. The `getPosts` function fetches the posts from an external API (`<a href="https://jsonplaceholder.typicode.com/posts?_limit=4`">https://jsonplaceholder.typicode.com/posts?_limit=4`</a>), ensuring that the data is not cached (`cache: no-store`), meaning it fetches fresh data on every request. The `homepage` component then renders a list of the post titles. The expected output is a page showing a list of four blog post titles, updated every time the page is loaded.</p><p>```javascript<br>async function getPosts() {<br> const res = await fetch(‘<a href="https://jsonplaceholder.typicode.com/posts?_limit=4&#39;">https://jsonplaceholder.typicode.com/posts?_limit=4&#39;</a>, {<br> cache: ‘no-store’,<br> });<br> return res.json();<br>}</p><p>const HomePage = async () =&gt; {<br> const posts = await getPosts();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’ }}&gt;<br> &lt;h1 style={{ color: ‘#333’}}&gt;Server-Side Rendering with getServerSideProps&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ color: ‘#555’, fontSize: ‘22px’, margin: ‘10px 0’ }}&gt;<br> {post.title}<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default HomePage;<br>```</p><p>This code above fetches a list of three blog posts from an API on every render but, due to the `no-store` cache setting, dynamically displays them as a list on the home page. The `getPosts` function handles the API call, and the posts are displayed using `map()` to generate `&lt;li&gt;` elements.</p><p>When the server renders the page, [Javascript](<a href="https://devdocs.io/javascript/">https://devdocs.io/javascript/</a>) calls the async `getServerSideProps` method. It retrieves the data and then returns an object containing the props key. The data retrieved is subsequently supplied to the page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LmWXJG4__enSiJSk.png" /></figure><p>The image produced above indicates that the data was properly fetched; otherwise, an error message is returned.</p><h4>Rendering with Fresh Data (API Calls on Every Request)</h4><p>Using the Fetch API directly in a server component can allow you to always fetch fresh data from the API on each request.</p><p>```javascript<br>// app/page.js</p><p>const fetchFreshPosts = async () =&gt; {<br> console.log(‘Fetching fresh data on every request’);<br> const res = await fetch(‘<a href="https://jsonplaceholder.typicode.com/posts?_limit=3&#39;">https://jsonplaceholder.typicode.com/posts?_limit=3&#39;</a>, {<br> cache: ‘no-store’, // No caching, fetches fresh data each time<br> });</p><p>if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>return res.json();<br>};</p><p>const FreshDataPage = async () =&gt; {<br> const posts = await fetchFreshPosts();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’ }}&gt;<br> &lt;h1 style={{ fontSize: ‘32px’, color: ‘#333’ }}&gt;Latest Blog Posts (Fresh Data)&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ marginBottom: ‘20px’ }}&gt;<br> &lt;h2 style={{ fontSize: ‘28px’, color: ‘#333’ }}&gt;{post.title}&lt;/h2&gt;<br> &lt;p style={{ fontSize: ‘22px’, color: ‘#555’ }}&gt;{post.body}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default FreshDataPage;<br>```</p><p>The `FreshDataPage` component fetches the latest blog posts from an API using the `fetchFreshPosts` function, which ensures that fresh data is retrieved on every request by setting the cache option to `’no-store’`. It handles potential fetch errors by throwing an exception if the response is not okay.</p><h4>Rendering with Cached Data (API Calls on Build)</h4><p>In contrast, if you want to cache the data and only fetch it on build time or based on a revalidation strategy, you can use the code below.</p><p>```javascript<br>// app/static-page.js</p><p>const fetchCachedPosts = async () =&gt; {<br> console.log(‘Fetching data and caching it for 30 minutes’);<br> const res = await fetch(‘<a href="https://jsonplaceholder.typicode.com/posts?_limit=3&#39;">https://jsonplaceholder.typicode.com/posts?_limit=3&#39;</a>, {<br> next: { revalidate: 1800 }, // Cache the data and revalidate every hour<br> });</p><p>if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>return res.json();<br>};</p><p>const CachedDataPage = async () =&gt; {<br> const posts = await fetchCachedPosts();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’ }}&gt;<br> &lt;h1 style={{ fontSize: ‘32px’, color: ‘#333’ }}&gt;Rendering with Cached Data (API Calls on Build)&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ marginBottom: ‘20px’ }}&gt;<br> &lt;h2 style={{ fontSize: ‘28px’, color: ‘#333’ }}&gt;{post.title}&lt;/h2&gt;<br> &lt;p style={{ fontSize: ‘22px’, color: ‘#555’ }}&gt;{post.body}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default CachedDataPage;<br>```</p><p>The `CachedDataPage` component fetches data from an API using the `fetchCachedPosts` function, which implements a caching mechanism to store the data for 30 minutes (1800 seconds). When the component is rendered, it displays a list of posts with their titles and bodies in a styled layout with a yellow background. If the data fetch fails, an error is thrown, ensuring that only valid responses are processed and displayed.</p><h4>Static Site Generation with getStaticProps</h4><p>`GetStaticProps` allows you to retrieve data during construction. This strategy works best for pages pre-rendered with data that does not change frequently. The code sample is below.</p><p>The code below will create a simple blog page in a Next.js app that fetches and displays the latest six blog posts. The `HomePage` function fetches data from an external API (`<a href="https://jsonplaceholder.typicode.com/posts?_limit=6`">https://jsonplaceholder.typicode.com/posts?_limit=6`</a>) on the server side, which means the data is retrieved when the page is requested. The page then renders a list of the post titles. The expected output is a webpage displaying six blog post titles under the heading Latest Blog Posts, showing updated content each time the page is loaded.</p><p>```jsx<br>export default async function HomePage() {<br> // Fetch data directly in the component (server-side)<br> const res = await fetch(“<a href="https://jsonplaceholder.typicode.com/posts?_limit=6">https://jsonplaceholder.typicode.com/posts?_limit=6</a>&quot;);<br> const posts = await res.json();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, padding: ‘45px’ }}&gt;<br> &lt;h1&gt;Static Site Generation with getStaticProps&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>}<br>```</p><p>This code above renders a list of blog posts fetched from an API at build time using `getStaticProps`, displaying the titles of the first six posts. The post `prop` is passed to the component and mapped to render each post’s title in an unordered list `&lt;ul&gt;`.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Z7At87GvByaFasV4.png" /></figure><p>The output image above indicates that the data has been correctly fetched; if not, an error message will be returned.</p><p>Unlike `getServerSideProps`, which fetches the list of posts on each request, `getStaticProps` fetches it during the build time. This strategy improves performance by serving the page as static [HTML](<a href="https://developer.mozilla.org/en-US/docs/Web/HTML">https://developer.mozilla.org/en-US/docs/Web/HTML</a>), faster than server-side-produced pages for each request.</p><p>When using `getStaticProps` and needing to update the data regularly, there was an option to use incremental static regeneration ([ISR](<a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration">https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration</a>)) by including a revalidate time in the return of `getStaticProps`.</p><h4>Evolution of Next.js 14: Beyond getServerSideProps and getStaticProps</h4><p>With the introduction of Server components in Next.js 13, much has changed in Next.js. For example, you no longer require methods such as `getServerSideProps` and `getStaticProps` to handle data fetching.</p><p>You can now utilize the fetch web API to conduct fetch requests in your components using `async/await` with the code below.</p><p>```jsx<br>‘use client’; // This marks the component as a Client Component</p><p>import React, { useEffect, useState } from ‘react’;</p><p>const HomePage = () =&gt; {<br> const [posts, setPosts] = useState([]);<br> const [error, setError] = useState(null);</p><p>useEffect(() =&gt; {<br> async function fetchPosts() {<br> try {<br> const res = await fetch(‘<a href="https://jsonplaceholder.typicode.com/posts?_limit=7&#39;">https://jsonplaceholder.typicode.com/posts?_limit=7&#39;</a>, {<br> next: { revalidate: 10 }, // Revalidate every 10 seconds (ISR behavior)<br> });</p><p>if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>const data = await res.json();<br> setPosts(data);<br> } catch (err) {<br> setError(err.message);<br> }<br> }</p><p>fetchPosts();<br> }, []);</p><p>if (error) {<br> return &lt;div&gt;Error: {error}&lt;/div&gt;;<br> }</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1 style={{ fontSize: ‘36px’}}&gt;Evolution of Next.js 14: Beyond getServerSideProps and getStaticProps&lt;/h1&gt;<br> &lt;h2 style={{ fontSize: ‘28px’}}&gt;openreplay&lt;/h2&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id} style={{ fontSize: ‘24px’}}&gt;{post.title}&lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>}</p><p>export default HomePage;<br>```</p><p>The code above helps create a simple webpage that shows a list of posts like short messages from a website. In the first part, we have a function called `fetchPosts` that goes to the linked website to get seven posts. It uses `fetch`, like sending a request asking for data. If the request works, it returns the posts in a way the webpage can understand. If the request fails, it gives an error. The cool part is that it rechecks the website every ten seconds to see if any posts have changed or new ones have been added.</p><p>The second part of the code is the `Homepage` function. This function uses the posts from the other website and displays them in a list `&lt;li&gt;`.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZfnZpMvIzH9htx6x.png" /></figure><p>According to the image output above, it shows that the data is fetched successfully if not, it will return an error message as an output.</p><p>This solution simplifies data retrieval by leveraging conventional web APIs and `async/await` for more understandable asynchronous code, dramatically shifting how data is handled in Next.js apps.</p><h4>Caching in Next.js 14</h4><p>Caching improves the retrieved Web API by allowing for server-side caching and revalidation. By default, Next.js caches the returned fetch values in the server’s data cache. Data can be gathered during the build or request process, cached, and utilized for subsequent requests.</p><p>```javascript<br>//’force-cache’ is the default caching strategy<br>fetch(‘<a href="https://...&#39;">https://...&#39;</a>, { cache: ‘force-cache’ });<br>```</p><p>The `force-cache` strategy in the fetch API forces the browser to use a cached response if available, even if the resource has expired, before making a network request.</p><p>For cases requiring new data on each request, the cache option can be set to `no-store` to bypass the cache entirely:</p><p>```javascript<br>fetch(‘<a href="https://api.example.com/posts&#39;">https://api.example.com/posts&#39;</a>, { cache: ‘no-store’ });<br>```<br>Furthermore, [Route Segment Config](<a href="https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config">https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config</a>) settings allow developers to turn off caching for specified route segments, affecting all data requests inside that segment.</p><p>```javascript<br>/ Disable caching for this route segment<br>export const dynamic = ‘force-dynamic’;<br>```</p><p>The code command above disables caching for a specific route segment in Next.js by forcing the content to be dynamically generated on every request, ensuring the latest data is served.</p><p>View all the available cache options in the [fetch](<a href="https://nextjs.org/docs/app/api-reference/functions/fetch">https://nextjs.org/docs/app/api-reference/functions/fetch</a>) API reference.</p><h4>Example of Caching with the Fetch API in Next.js 14</h4><p>In this example, we’ll fetch blog posts directly in a React component and leverage the built-in caching capabilities of the Fetch API. This approach allows you to cache the fetched data automatically.</p><p>```javascript<br>const fetchUsers = async () =&gt; {<br> const res = await fetch(‘<a href="https://reqres.in/api/users?page=1&#39;">https://reqres.in/api/users?page=1&#39;</a>, {<br> next: { revalidate: 3600 }, // Cache data for 1 hour<br> });</p><p>if (!res.ok) {<br> throw new Error(‘Failed to fetch data’);<br> }</p><p>const data = await res.json();<br> return data.data; // Return the users array<br>};</p><p>const UsersPage = async () =&gt; {<br> try {<br> const users = await fetchUsers();</p><p>return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Example of Caching with the Fetch API in Next.js 14; Cached Users&lt;/h1&gt;<br> &lt;ul&gt;<br> {users.map((user) =&gt; (<br> &lt;li key={user.id} style={{ marginBottom: ‘20px’ }}&gt;<br> &lt;h2&gt;{`${user.first_name} ${user.last_name}`}&lt;/h2&gt;<br> &lt;p&gt;Email: {user.email}&lt;/p&gt;<br> &lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br> } catch (error) {<br> return (<br> &lt;div style={{ backgroundColor: ‘#FFFF99’, minHeight: ‘100vh’, padding: ‘20px’, color: ‘black’ }}&gt;<br> &lt;h1&gt;Error: {error.message}&lt;/h1&gt;<br> &lt;/div&gt;<br> );<br> }<br>};</p><p>export default UsersPage;<br>```</p><p>`fetchPosts` function uses the `Fetch` API to retrieve blog posts. By setting `next: { revalidate: 3600 }`, we specify that the data should be cached for one hour. After that time, the next request will fetch fresh data from the API. The posts are then rendered in the component, maintaining a consistent yellow background for styling, along with readable text colors and sizes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tgYIHbV3WXzBWbXa.png" /></figure><p>After entering the code above in your `app/page.js` file and you click on save, the image above is what will display in your terminal to show you are doing it right. Then you navigate to your browser to see the output on your localhost as shown below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0ak9-JJDUOoYTDH1.png" /></figure><p>The output image above indicates that the data has been correctly fetched; if not, an error message will be returned.</p><h4>Revalidating data in Next.js 14</h4><p>Revalidation involves clearing the data cache and re-fetching the most recent data. This is particularly handy when your data is dynamic and you must present the most up-to-date information.</p><p>The following codes can revalidate data at a specific time interval. Use the fetch revalidate option to set a resource’s cache lifespan in seconds.</p><p>```javascript<br>fetch(‘<a href="https://api.example.com/posts&#39;">https://api.example.com/posts&#39;</a>, { next: { revalidate: 3600 } });<br>```</p><p>Route segment `config` options also allow for `route-wide` revalidation settings:</p><p>```javascript<br>export const revalidate = 3600; // revalidate at most every hour<br>```</p><p>This code sets a revalidation interval of 3600 seconds (1 hour) for the export, meaning the data will be revalidated at most once every hour.</p><p>If you have many fetch queries in a statically rendered route, each with a unique revalidation frequency, all inquiries will be served at the fastest possible speed. However, each fetch request will be verified separately to render routes dynamically.</p><h4>Data fetching in client components in Next.js 14</h4><p>Next.js 14 allows you to use [Route handlers](<a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">https://nextjs.org/docs/app/building-your-application/routing/route-handlers</a>) or third-party libraries like [SWR](<a href="https://swr.vercel.app/docs/with-nextjs">https://swr.vercel.app/docs/with-nextjs</a>) or [TanStack Query](<a href="https://www.npmjs.com/package/@tanstack/react-query">https://www.npmjs.com/package/@tanstack/react-query</a>) to fetch data on the client side.</p><p>Let’s look at how to fetch data with route handlers.</p><p>* Route handlers run on the server and return data to the client, which is useful when you don’t want to share sensitive information with the client, such as API tokens.</p><p>* Route handlers use the [Web Request](<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest">https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest</a>) and [Response API](<a href="https://developer.mozilla.org/en-US/docs/Web/API/Response">https://developer.mozilla.org/en-US/docs/Web/API/Response</a>) to create custom request handlers for specific routes, they are defined as a [routes.js](<a href="https://www.npmjs.com/package/routes-js">https://www.npmjs.com/package/routes-js</a>) file within the app directory.</p><p>This code below will create a client component in a Next.js app that fetches five blog posts from an external API, manages loading states and errors with React hooks, and displays either a loading message, an error message, or a list of the post titles once the data is successfully retrieved.</p><p>```javascript<br>“use client”; // Ensure this is present at the very top</p><p>import { useState, useEffect } from “react”;</p><p>const HomePage = () =&gt; {<br> const [posts, setPosts] = useState([]);<br> const [loading, setLoading] = useState(true);<br> const [error, setError] = useState(null);</p><p>const fetchPosts = async () =&gt; {<br> setLoading(true);<br> try {<br> const response = await fetch(“<a href="https://jsonplaceholder.typicode.com/posts?_limit=5">https://jsonplaceholder.typicode.com/posts?_limit=5</a>&quot;);<br> if (!response.ok) {<br> throw new Error(“Failed to fetch posts”);<br> }<br> const data = await response.json();<br> setPosts(data);<br> } catch (error) {<br> setError(error.message);<br> } finally {<br> setLoading(false);<br> }<br> };</p><p>useEffect(() =&gt; {<br> fetchPosts();<br> }, []);</p><p>if (loading) {<br> return &lt;div&gt;Loading…&lt;/div&gt;;<br> }</p><p>if (error) {<br> return &lt;div&gt;Error: {error}&lt;/div&gt;;<br> }</p><p>return (<br> &lt;div&gt;<br> &lt;h1&gt;Data Fetching in Client Component Nextjs 14&lt;/h1&gt;<br> &lt;ul&gt;<br> {posts.map((post) =&gt; (<br> &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;<br> ))}<br> &lt;/ul&gt;<br> &lt;/div&gt;<br> );<br>};</p><p>export default HomePage;<br>```</p><p>The code above helps make a webpage that shows a list of 5 posts (short messages) from the linked webpage. First, it has a part called `useState` that keeps track of the posts, when the page is still loading, and if there’s any problem. But when the webpage starts, it runs a special function called `fetchPosts` to get the posts from the linked webpage. If the posts come back okay, it saves them to display. If something goes wrong, it shows an error message.</p><p>The `HomePage` function controls what you see on the screen. While the posts are being loaded, it shows loading…, and if there’s an error, it shows a message with what went wrong. Once the posts are fetched, they get displayed in an unordered list `&lt;ul&gt;`, each with its spot. This way, the webpage can grab information from the internet and show it neatly. Output can be seen in the image below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*U5ujDqGCH3Sa2E0O.png" /></figure><p>Based on the image output above, it appears that the data was successfully fetched. If not, an error message will be returned as the output.</p><h4>Conclusion</h4><p>In Next.js 14, data fetching has evolved from the early use of `getInitialProps` to more sophisticated methods like `getServerSideProps` and `getStaticProps`, introduced in Next.js 9.3, which improved efficiency by fetching data on every request or during build time. With the advent of Server Components and Fetch API in Next.js 13, developers now fetch data directly within components using `async/await`, simplifying the process and eliminating the need for earlier methods. Client-side data fetching is also enhanced, utilizing React hooks and libraries like [SWR](<a href="https://swr.vercel.app/docs/with-nextjs">https://swr.vercel.app/docs/with-nextjs</a>) and [TanStack Query](<a href="https://tanstack.com/query/latest/docs/framework/react/overview">https://tanstack.com/query/latest/docs/framework/react/overview</a>) for dynamic data management. The caching and revalidation features introduced in Next.js 14 enable control over data freshness and improve performance by optimizing how and when data is retrieved or updated.</p><p>In summary, Next.Js 14 offers an advanced and flexible data-fetching ecosystem, blending server and client-side strategies to efficiently handle dynamic content, caching, and real-time data updates.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=997518eff0f4" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>