<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: RitzaCo</title>
    <description>The latest articles on DEV Community by RitzaCo (@ritzaco).</description>
    <link>https://dev.to/ritzaco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F509329%2Ff5b1a3db-bdb1-4f41-a222-770a118a8156.jpg</url>
      <title>DEV Community: RitzaCo</title>
      <link>https://dev.to/ritzaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ritzaco"/>
    <language>en</language>
    <item>
      <title>Voxtral TTS vs ElevenLabs: Voice Cloning &amp; TTS</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 12:25:29 +0000</pubDate>
      <link>https://dev.to/ritza/voxtral-tts-vs-elevenlabs-voice-cloning-tts-4g6e</link>
      <guid>https://dev.to/ritza/voxtral-tts-vs-elevenlabs-voice-cloning-tts-4g6e</guid>
      <description>&lt;p&gt;If you're choosing between Voxtral TTS and ElevenLabs, it comes down to whose voice you need. With stock voices, ElevenLabs won every test I ran. It sounded human, followed every emotion tag, and got the name, date and order number right where Voxtral didn't. Voxtral sounded rushed and flat, like text-to-speech from a couple of years ago. Cloning my own voice went the other way: Voxtral kept my South African accent, even from five seconds of audio, while ElevenLabs gave me a thick British accent.&lt;/p&gt;

&lt;p&gt;Mistral released Voxtral 3 on 20 October 2026, and the TTS model is in public preview. I tested the things Mistral says are new against Eleven v4, using the same scripts I used in my &lt;a href="https://techstackups.com/comparisons/fish-audio-vs-elevenlabs/" rel="noopener noreferrer"&gt;Fish Audio&lt;/a&gt; and &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; comparisons, so you can compare ElevenLabs across all three, and every clip is below to listen to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voxtral TTS vs ElevenLabs: the quick verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;One-line reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Natural delivery (no tags)&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Conversational; Voxtral rushed through it like it was reading from a sheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emotional direction (tags)&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Followed every tag; Voxtral ignored one, overdid another and read the rest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Names, numbers and currency&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Got the name, date and order number right. Voxtral read the amount and phone number better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixing a pronunciation&lt;/td&gt;
&lt;td&gt;No fix&lt;/td&gt;
&lt;td&gt;Voxtral's phoneme hint didn't fix the name, and the rest of the take got worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice cloning: likeness&lt;/td&gt;
&lt;td&gt;Voxtral&lt;/td&gt;
&lt;td&gt;Kept my accent and tone, even from 5 seconds of audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice cloning: audio quality&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Cleaner recording, but it didn't sound like me&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;French&lt;/td&gt;
&lt;td&gt;Not judged&lt;/td&gt;
&lt;td&gt;I can't verify French, so the clips are here for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API price&lt;/td&gt;
&lt;td&gt;Voxtral&lt;/td&gt;
&lt;td&gt;$13 per million characters against $80 for Eleven v4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How we tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Models.&lt;/strong&gt; Voxtral TTS 3 (&lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;) against Eleven v4 (&lt;code&gt;eleven_v4&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Through the APIs.&lt;/strong&gt; Mistral AI Studio has a text-to-speech page, but its voice picker only offered preset voices, with no way to clone. So I generated everything through the two APIs: a Mistral API account with prepaid credits, and an existing paid ElevenLabs Creator account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stock voices.&lt;/strong&gt; ElevenLabs' Global Artist - Newsreader against Voxtral's Mary. Both are British female voices. I picked Mary from three Voxtral British voices as the closest match to the Newsreader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same words on both.&lt;/strong&gt; Only the tag syntax differs, because each tool writes tags its own way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single take, default settings.&lt;/strong&gt; Every clip is the first output from one generation. I allowed one deliberate fix per test, labelled separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One listener.&lt;/strong&gt; These are my impressions, apart from a transcription pass I used to check that no clip skipped any text.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Voxtral TTS vs ElevenLabs: natural delivery
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; Mistral's headline claim that Voxtral 3 voices "adjust their emotion, pace, and emphasis based on conversational context" with no instructions. The script is someone getting good news at work, then remembering it isn't public yet, read as plain text with no tags.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wasn't going to tell anyone. We got the Lisbon project! I read the email three times before I believed it. Anyway, don't tell anyone yet. It's not official until Friday. Right. Now I just have to work out how to tell the team without grinning like an idiot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Natural read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Plain text, no tags. Does the voice infer the excitement, the secrecy and the pause?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s1-natural-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: Natural read (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;. Rushed; excitement sounds forced&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s1-natural-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: Natural read (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;. Conversational, shifts emotion on its own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs sounded more natural and more conversational. At 0:02 it puts secrecy into "I wasn't going to tell anyone" and then switches to excitement, all inferred from the text.&lt;/p&gt;

&lt;p&gt;Voxtral found the same moments: the excitement, the change at "Anyway" and the pause at "Right." But it read the whole thing as if it were in a hurry, and the excitement on "Lisbon project" at 0:02 sounds forced. It sounded like someone reading from a sheet of paper. Its clip is 15 seconds against ElevenLabs' 17, and you can hear the difference. I'd use the ElevenLabs take as it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voxtral TTS emotion tags vs ElevenLabs audio tags
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; Voxtral 3's new inline tags. Mistral documents 34 emotions, pace control, and sounds like &lt;code&gt;[laugh]&lt;/code&gt; and &lt;code&gt;[sigh]&lt;/code&gt;. Voxtral has no whisper tag, so I swapped the whisper from my &lt;a href="https://techstackups.com/comparisons/fish-audio-vs-elevenlabs/" rel="noopener noreferrer"&gt;Fish Audio test&lt;/a&gt; for directions both tools support, and kept the same words and voices as before.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Moment&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;th&gt;Voxtral&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"We got the Lisbon project!"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[excited]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[emotion=excited]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Anyway, don't tell anyone yet."&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[anxious]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[emotion=anxious]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"It's not official until Friday."&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[slowly]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[pace=slow]&lt;/code&gt;, then &lt;code&gt;[pace=moderate]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before "Right."&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[sighs]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[sigh]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before the last sentence&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[laughs]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[laugh]&lt;/code&gt;, then &lt;code&gt;[emotion=amused]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Voxtral tags last until the next tag of the same kind, so its script needs &lt;code&gt;[pace=moderate]&lt;/code&gt; after "Friday" to get back to normal speed. ElevenLabs tags don't need resetting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Directed read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Excitement, anxiety, a slow line, a sigh and a laugh, in the same places on both&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s2-directed-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: Directed read (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;. Ignored 'anxious', overdid 'slow'&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s2-directed-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: Directed read (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;. Every tag landed; the laugh carries over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs followed every direction and still inferred emotion where there was no tag, like the first sentence at 0:01. At 0:13 it moves from the sigh into the laugh naturally, and the laugh carries into the next sentence instead of stopping dead before it.&lt;/p&gt;

&lt;p&gt;Voxtral's emotions sounded read rather than acted. At 0:02 the &lt;code&gt;[excited]&lt;/code&gt; sentence sounds the same as the untagged sentence before it. "Anyway, don't tell anyone yet" has no emotion at all, so &lt;code&gt;[anxious]&lt;/code&gt; was ignored. Then at 0:09 it overdoes the slow line so much that I was waiting for it to finish, and against the rushed pace of everything else the change is very obvious. The sigh and the laugh both happen, but they sound like the model was told to do them. The pace reset did work: it went back to normal speed after "Friday".&lt;/p&gt;

&lt;p&gt;Direction improved both tools compared with their natural reads, but Voxtral still felt rushed. ElevenLabs used the tags to colour the whole paragraph, while Voxtral applied each emotion to its own sentence and moved on. I didn't hear either tool read a tag aloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Names, numbers and currency
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; the kind of appointment message a clinic might send. This is where I expected Voxtral to do best, because Mistral says it benchmarked Voxtral 3 on numbers and unusual names, and on local formats like dates and currencies. Neither script had tags.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi, it's Dr. Nkosi's office. Your appointment is on 3 April 2026 at 14:45. The total is R1,249.50. Your booking reference is A-7419-X. If you need to change it, call 0800 555 0147. For the video appointment, please use the 2.4 GHz network if the other connection drops. Thanks, and we'll see you on Friday.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Names and numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Name, date, time, rand amount, booking reference, phone number, frequency&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s3-practical-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: Names and numbers (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;. Better amount and phone number, dropped a dash&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s3-practical-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: Names and numbers (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;. Consistently correct&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Voxtral&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dr. Nkosi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"nir-kow-see", wrong&lt;/td&gt;
&lt;td&gt;Correct, "n-kaw-see"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3 April 2026&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"on third April", which in that order should be "on the third of April"&lt;/td&gt;
&lt;td&gt;"on April third", natural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;R1,249.50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"one thousand two hundred and forty-nine rand and fifty cents", the more natural phrasing&lt;/td&gt;
&lt;td&gt;"one thousand two hundred forty-nine rand and fifty cents", correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A-7419-X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Left out the second dash&lt;/td&gt;
&lt;td&gt;Both dashes spoken&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0800 555 0147&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"zero eight hundred", which sounds more professional&lt;/td&gt;
&lt;td&gt;"oh eight oh oh", correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both read 14:45 and 2.4 GHz correctly.&lt;/p&gt;

&lt;p&gt;This was Voxtral's best test, and I didn't expect it to do this well after the first two. It read the rand amount and the phone number better than ElevenLabs. In my Fish comparison I wished ElevenLabs would say "oh-eight-hundred" for 0800, and Voxtral's "zero eight hundred" is close to that. It was also less rushed here, at a perfectly reasonable speed.&lt;/p&gt;

&lt;p&gt;But ElevenLabs was more consistently correct, and the error I care about most is Voxtral's. Leaving out a dash in a booking reference is a problem when someone has to read it back. ElevenLabs' take needs less editing before I'd use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trying Voxtral's pronunciation fix
&lt;/h3&gt;

&lt;p&gt;Voxtral lets you tell it exactly how to say a word with &lt;code&gt;phoneme_hints&lt;/code&gt;, which maps a word to IPA phonemes. Studio also has a Pronunciations panel. I gave "Nkosi's" the sounds &lt;code&gt;ŋ k ɔ s i z&lt;/code&gt; and regenerated the same message once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s3-practical-voxtral-fix1.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral pronunciation fix (MP3)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It didn't work: the name changed from "nir-kow-see" to "nin-kaw-see": the vowel is right now, but there's an extra sound at the start. And because the fix regenerates the whole message, everything else changed too. The rand amount lost the "and" that I liked, and the booking reference lost both dashes this time. I couldn't get the name right, and the takes were very inconsistent between generations, so for me it wasn't worth the effort. You also have to write the hint in IPA, which most people won't know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voxtral TTS voice cloning vs ElevenLabs voice cloning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; whether each tool sounds like me from a short recording, and keeps sounding like me when the mood changes. Mistral says Voxtral can clone a voice "from as little as 2–3 seconds of audio, capturing emotion, speaking style, and accent". I'm South African, so accent drift is easy for me to hear.&lt;/p&gt;

&lt;p&gt;I used the same recording of my voice as in my Fish and Gemini comparisons, but Voxtral rejected it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reference audio duration 33.90s exceeds the maximum allowed duration of 30.0s&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mistral's docs say a reference can be "a few seconds to a few minutes", so that limit came as a surprise. I cut the recording at a natural pause to 29.8 seconds and gave that file to both tools, so ElevenLabs had about four seconds less of my voice than in the earlier articles.&lt;/p&gt;

&lt;p&gt;On Voxtral the recording is sent with each request and nothing is saved. On ElevenLabs I made a temporary instant clone with background noise removal off, then deleted it after the tests. Both clones read a passage I never recorded:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I left my keys next to the kettle again. If you're heading out, could you check whether the back door is locked? I should be home before the rain starts. Oh, and the good news: they finally approved the project! I can stop refreshing my inbox every five minutes. Let's get dinner somewhere nice tonight.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Instant voice cloning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same 29.8 s recording of my voice, same new text&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s4-clone-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: Instant voice cloning (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;. Keeps my accent and tone&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s4-clone-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: Instant voice cloning (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;. Cleaner audio, British accent over mine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voxtral wins clearly, because it kept my accent and tone. At 0:09 the word "finally" sounds very close to how I say it, and "They finally approved the project!" still sounded like me, without the rushed pace of its stock voice.&lt;/p&gt;

&lt;p&gt;The ElevenLabs clone is the cleaner recording, but it doesn't sound much like me. There's a thick British accent laid over my own, and the expressive parts sound nothing like me. It's the same thing I heard in my &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;Gemini comparison&lt;/a&gt;. My impression is that ElevenLabs gives up a lot of the original voice to make the clone expressive, and the result sounds more like a stock voice. Voxtral struggles with expression, but it keeps what makes the voice mine.&lt;/p&gt;

&lt;p&gt;If I had to use one of these clones, I'd use Voxtral.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloning from five seconds
&lt;/h3&gt;

&lt;p&gt;To test Mistral's short-reference claim, I also cloned on Voxtral from just the first 4.9 seconds of the same recording. ElevenLabs needs at least 10 seconds, so this one is Voxtral only.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s4-clone-voxtral-5s.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral clone from five seconds (MP3)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was surprisingly good, almost as good as the 29.8-second clone. It sounds a little flatter, but it still sounded more like me than ElevenLabs' clone from six times as much audio. That's one voice, mine, but it backs up Mistral's claim.&lt;/p&gt;

&lt;p&gt;Only clone a voice you own or have permission to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Voxtral TTS languages: a French test
&lt;/h2&gt;

&lt;p&gt;Voxtral 3 supports 35 languages, with stock voices in 22 of them, so I generated a short French version of the Lisbon scene on both. It includes a date, a time and a euro amount written the French way. I don't speak French well enough to judge accent or pronunciation, so there's no verdict here, and you can listen and decide for yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Je ne voulais le dire à personne. On a décroché le projet de Lisbonne! J'ai relu l'e-mail trois fois avant d'y croire. La réunion de lancement est le 3 avril à 14 h 45, et le budget est de 1 249,50 €. Bon, n'en parle à personne avant vendredi.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The stock voices are Voxtral's Marie and ElevenLabs' Claire, a native French library voice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;French, stock voices&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Plain text with a date, a time and a euro amount&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s5-french-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: French, stock voices (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s5-french-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: French, stock voices (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mistral also says a cloned voice can speak languages other than the one it was recorded in. These are my two clones from the English recording, speaking French:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;French, cloned voice&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clones made from an English recording, reading the same French text&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s5-french-clone-voxtral.mp3" rel="noopener noreferrer"&gt;Listen to Voxtral: French, cloned voice (MP3)&lt;/a&gt; — &lt;code&gt;voxtral-mini-tts-3-0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/voxtral-vs-elevenlabs/vx-s5-french-clone-elevenlabs.mp3" rel="noopener noreferrer"&gt;Listen to ElevenLabs: French, cloned voice (MP3)&lt;/a&gt; — &lt;code&gt;eleven_v4&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one thing I can judge is likeness: the Voxtral clone sounds like English-speaking me reading French from a piece of paper. I'm not sure that's a good thing, but for a clone made from an English recording it's more accurate than the ElevenLabs one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Voxtral TTS free? Voxtral vs ElevenLabs pricing
&lt;/h2&gt;

&lt;p&gt;Yes, you can create TTS clips with Voxtral Mini TTS 3.0 in Mistral Studio on a free account, with a 4,000-character input limit per generation. Free accounts don't have API access. The UI doesn't show a total free token or character allowance, so the 4,000-character limit shouldn't be read as your total free quota.&lt;/p&gt;

&lt;p&gt;I used an API account with prepaid credits for the comparisons in this article. In Studio, I could generate speech with preset voices but saw no cloning option. API prices below were checked on 26 October 2026, in US dollars; free-plan access was checked on 27 October 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Voxtral&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API price for the model I tested&lt;/td&gt;
&lt;td&gt;Voxtral TTS 3: $13 per million characters&lt;/td&gt;
&lt;td&gt;Eleven v4: $80 per million characters ($0.08 per 1,000)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cheaper option&lt;/td&gt;
&lt;td&gt;None for TTS 3&lt;/td&gt;
&lt;td&gt;Eleven v4 Turbo: $40 per million characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free plan&lt;/td&gt;
&lt;td&gt;Free TTS in Studio: 4,000 characters per generation; no API access; total allowance not shown&lt;/td&gt;
&lt;td&gt;10,000 credits a month on the website, no instant cloning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloning on the website&lt;/td&gt;
&lt;td&gt;No cloning option in Studio when I tested&lt;/td&gt;
&lt;td&gt;From Starter, $6 a month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On paper, Voxtral is about six times cheaper than Eleven v4, but in practice I don't think the gap is that big. Most of the ElevenLabs takes in this article were usable as they were, and none of the Voxtral stock-voice takes were, so you'd spend more generations getting a usable Voxtral clip. The cost per usable clip ends up closer than the list prices suggest.&lt;/p&gt;

&lt;p&gt;For scale, these tests used 1,938 characters on my ElevenLabs account, about $0.16 at API rates. The Voxtral requests, including three short samples I used to pick a voice, came to about 2,600 characters, about $0.03 at list price.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://docs.mistral.ai/inference/pricing" rel="noopener noreferrer"&gt;Mistral API pricing&lt;/a&gt;, &lt;a href="https://elevenlabs.io/pricing/api" rel="noopener noreferrer"&gt;ElevenLabs API pricing&lt;/a&gt;, &lt;a href="https://elevenlabs.io/pricing" rel="noopener noreferrer"&gt;ElevenLabs plans&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note for developers using Voxtral's MP3 output
&lt;/h3&gt;

&lt;p&gt;Voxtral returns MP3s without the header that tells a player how long the file is. My player showed the first clip as 16 seconds long, stopped at 14, and wouldn't replay it. I fixed every Voxtral clip in this article by rewriting the header with &lt;code&gt;ffmpeg -i in.mp3 -c:a copy -write_xing 1 out.mp3&lt;/code&gt;, which leaves the audio untouched. If you serve Voxtral MP3s directly to users, request WAV or fix the header first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Voxtral TTS open source?
&lt;/h2&gt;

&lt;p&gt;Voxtral TTS 3, the model I tested, isn't: Mistral hasn't published its weights, and self-hosting it means contacting Mistral's sales team. The older Voxtral TTS from March 2026 has &lt;a href="https://huggingface.co/mistralai/Voxtral-4B-TTS-2603" rel="noopener noreferrer"&gt;open weights on Hugging Face&lt;/a&gt;, but under a non-commercial licence (CC BY-NC 4.0), so you can run it locally for personal projects but not in a paid product. That March model is the Voxtral I included in my &lt;a href="https://techstackups.com/comparisons/best-tts-2026-blind-benchmark/" rel="noopener noreferrer"&gt;16-provider blind TTS benchmark&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Voxtral a good ElevenLabs alternative?
&lt;/h2&gt;

&lt;p&gt;For cloning your own voice, yes. For ordinary text to speech, not yet: ElevenLabs is significantly more refined and ready for commercial use, and Voxtral still feels slightly like a pet project. If you want something that works out of the box, use ElevenLabs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Voiceovers and anything with emotion:&lt;/strong&gt; ElevenLabs. It sounded human, and it followed direction. Voxtral sounded flat and rushed in every stock-voice test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices, references and phone numbers:&lt;/strong&gt; ElevenLabs. Voxtral read some numbers more naturally, but it dropped a dash in a booking reference and got the date order wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloning your own voice:&lt;/strong&gt; Voxtral. It kept my accent where ElevenLabs didn't, even from five seconds of audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost at scale:&lt;/strong&gt; Voxtral's list price is far lower, but check how many takes you need before assuming it's cheaper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This covers two models, one listener and a handful of short tests. For a wider view, see my &lt;a href="https://techstackups.com/comparisons/fish-audio-vs-elevenlabs/" rel="noopener noreferrer"&gt;Fish Audio vs ElevenLabs&lt;/a&gt; and &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;Gemini vs ElevenLabs&lt;/a&gt; comparisons, and for live conversation, &lt;a href="https://techstackups.com/comparisons/best-voice-agent-2026/" rel="noopener noreferrer"&gt;the best realtime voice agent in 2026&lt;/a&gt;. I didn't test Voxtral's voice agent SDK, its professional voice cloning, or long-form narration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is ElevenLabs good for voice cloning?
&lt;/h3&gt;

&lt;p&gt;ElevenLabs makes clean, expressive clones, but they haven't sounded like me. In this article, my &lt;a href="https://techstackups.com/comparisons/fish-audio-vs-elevenlabs/" rel="noopener noreferrer"&gt;Fish Audio comparison&lt;/a&gt; and my &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;Gemini comparison&lt;/a&gt;, its instant clone of my South African voice came out with a British accent. Its professional voice cloning, which needs more audio, may do better, but I haven't tested it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Voxtral TTS support voice cloning?
&lt;/h3&gt;

&lt;p&gt;Yes. Voxtral TTS 3 clones a voice from a short recording sent with each request, up to 30 seconds long, in MP3 or WAV. It worked through the API, on an account with prepaid credits, but the Studio website showed no cloning option. Mistral also offers professional voice cloning from 30 minutes to 3 hours of audio, which I didn't test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Voxtral better than ElevenLabs?
&lt;/h3&gt;

&lt;p&gt;Only for cloning your own voice, where Voxtral's clone sounded more like me. For ordinary text to speech with stock voices, ElevenLabs was better at emotion, direction and getting names and references right.&lt;/p&gt;

</description>
      <category>elevenlabs</category>
      <category>tts</category>
      <category>voxtral</category>
      <category>ai</category>
    </item>
    <item>
      <title>Firecrawl vs Tavily vs Exa vs DeepSeek: Best Search API for AI Agents (2026)</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Tue, 27 Oct 2026 09:36:54 +0000</pubDate>
      <link>https://dev.to/ritza/firecrawl-vs-tavily-vs-exa-vs-deepseek-best-search-api-for-ai-agents-2026-27cc</link>
      <guid>https://dev.to/ritza/firecrawl-vs-tavily-vs-exa-vs-deepseek-best-search-api-for-ai-agents-2026-27cc</guid>
      <description>&lt;p&gt;Python 3.15.1 came out on 21 October 2026. Two days later, I asked four search APIs built for AI agents for the latest stable Python release, and only one of them knew. Exa returned the 3.15.1 release page. Firecrawl, Tavily and DeepSeek returned pages that still named 3.15.0 or 3.14, and an agent reading those results would have given the wrong answer without knowing it.&lt;/p&gt;

&lt;p&gt;Because an agent trusts whatever its search tool hands back, I ran 40 searches, 40 page fetches and two site crawls through all four tools. DeepSeek's Search API is the newest of them, launched on 17 October at $0.75 per 1,000 calls, about a ninth of Exa's price.&lt;/p&gt;

&lt;p&gt;Exa returned the best search results for 31 of the 40 queries, so it's the one to search with. For reading pages, use Firecrawl, which got usable content from 37 of 40 pages, including every PDF, and was the only tool that crawled a docs site reliably. DeepSeek was the cheapest and fastest at search, but it couldn't fetch a single PDF. Tavily returned the leanest results, but it didn't win any category outright. Everything ran on free plans, apart from five cents of DeepSeek credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What these tools actually do
&lt;/h2&gt;

&lt;p&gt;All four give an AI agent access to the live web, through three kinds of call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search:&lt;/strong&gt; send a query and get back ranked results with page text, ready to drop into a model's context. This is what Exa, Tavily and DeepSeek lead with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch (or scrape):&lt;/strong&gt; send a URL and get back the page as clean markdown, without the navigation, ads and scripts. This is Firecrawl's core product, and the other three offer it too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawl:&lt;/strong&gt; send a site and get back many pages. Only Firecrawl and Tavily do this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A SERP API such as SerpApi is a different product: it scrapes Google and returns Google's results as JSON. The four tools here run their own search index or a mix of sources, and return page content for a model to read. Our &lt;a href="https://techstackups.com/comparisons/best-serp-api-comparison-serpapi-exa-tavily/" rel="noopener noreferrer"&gt;best SERP API comparison&lt;/a&gt; covers that category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firecrawl vs Tavily vs Exa vs DeepSeek: the quick verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;One-line reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Answer a question with current facts&lt;/td&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;Only tool that surfaced Python 3.15.1, released two days earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find news from the last 48 hours&lt;/td&gt;
&lt;td&gt;Exa, with DeepSeek close&lt;/td&gt;
&lt;td&gt;Exa won 7 of 8 news queries; DeepSeek returned fresh coverage for 40 of 40 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Look up docs for a coding agent&lt;/td&gt;
&lt;td&gt;Exa, with Tavily close&lt;/td&gt;
&lt;td&gt;Exa found the official doc page for all 8 queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research and "find similar"&lt;/td&gt;
&lt;td&gt;Exa, by far&lt;/td&gt;
&lt;td&gt;33 distinct matching companies, papers and essays, against 23 for the next best&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regional and non-English queries&lt;/td&gt;
&lt;td&gt;Exa, with DeepSeek on Chinese&lt;/td&gt;
&lt;td&gt;Exa won 7 of 8; DeepSeek won the Spanish query and tied on the Chinese one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn a URL into clean markdown&lt;/td&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;37 of 40 pages returned usable content, including all 10 PDFs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crawl a docs site&lt;/td&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;25 of 25 pages on both sites; Tavily's crawls were unreliable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lowest price&lt;/td&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;$0.75 per 1,000 searches or fetches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smallest payload for an agent&lt;/td&gt;
&lt;td&gt;Tavily&lt;/td&gt;
&lt;td&gt;About 5,600 characters per search, against 68,000 for Firecrawl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How we tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plans.&lt;/strong&gt; Firecrawl, Exa and Tavily on new free accounts, with no card. DeepSeek has no free tier for its API, so I topped up the $2 minimum, of which the tests used $0.05.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search.&lt;/strong&gt; 40 queries, 8 for each of five jobs, each run once, asking for 5 results.

&lt;ul&gt;
&lt;li&gt;Firecrawl returned page content (&lt;code&gt;scrapeOptions&lt;/code&gt;) for 32 of the 40 queries. Its free credits ran out before the 8 news queries, so those got titles and descriptions only.&lt;/li&gt;
&lt;li&gt;Exa used &lt;code&gt;type: auto&lt;/code&gt; with page text, Tavily ran at both &lt;code&gt;basic&lt;/code&gt; and &lt;code&gt;advanced&lt;/code&gt; depth, and DeepSeek used &lt;code&gt;need_content: true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grading.&lt;/strong&gt; Each query's five result sets were shuffled under anonymous labels and graded blind by an LLM, using a written rubric for each job.

&lt;ul&gt;
&lt;li&gt;Every tool's results were cut to the same 1,200-character excerpt: the opening plus the passages that best match the query's words.&lt;/li&gt;
&lt;li&gt;Current-facts answers were checked against official sources on the day, such as python.org, the Federal Reserve and the South African Reserve Bank.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch.&lt;/strong&gt; 40 URLs, in four groups of 10: static pages, JavaScript-heavy pages, PDFs and bot-protected sites. For each page I loaded it in Chrome, captured three to five exact sentences from the main content, and checked whether each tool's markdown contained them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawl.&lt;/strong&gt; docs.astral.sh/uv and docusaurus.io/docs, limited to 25 pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single runs.&lt;/strong&gt; Latency comes from one run per call, on 23 October 2026, so treat it as indicative.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Search results by job
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Current facts: Exa found the answer nobody else did
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; whether an agent could answer a factual question correctly from the results alone, when the answer changed recently.&lt;/p&gt;

&lt;p&gt;Exa scored 8.4 out of 10 and had the right answer in all 8 result sets. For the Python question in the introduction, all five of its results were python.org pages, including the 3.15.1 release page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;Tavily basic&lt;/th&gt;
&lt;th&gt;Tavily advanced&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average score&lt;/td&gt;
&lt;td&gt;6.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td&gt;6.5&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct answer present&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8/8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the other tools, the usual problem was stale pages mixed in with current ones. On the South African repo rate, every tool found the current 7.25%, but Tavily basic returned four pages still quoting the old 7%, which would mislead an agent that goes with what most of its sources say.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking news: Exa first, DeepSeek a close second
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; whether each tool surfaces coverage of an event from the last 48 hours, rather than older related stories.&lt;/p&gt;

&lt;p&gt;I wrote the 8 news queries on the morning of the test from that week's headlines, such as US jobless claims falling to 194,000 and Nvidia launching its DSX POD.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl*&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;Tavily basic&lt;/th&gt;
&lt;th&gt;Tavily advanced&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average score&lt;/td&gt;
&lt;td&gt;6.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;7.9&lt;/td&gt;
&lt;td&gt;8.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Results about the event (of 40)&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* Titles and descriptions only, because the free credits ran out.&lt;/p&gt;

&lt;p&gt;For the jobless claims query, Exa returned AP, Reuters, CNBC and the Department of Labor's own release, all with the 194,000 figure. DeepSeek also had the figure in every result, though two of its five were Bulgarian and Korean news sites. Tavily basic missed one query entirely, returning pages about AI in India for the Nvidia launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docs lookups for coding agents: Exa, then Tavily
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; whether a coding agent gets the official documentation page, with enough content to write correct code.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;Tavily basic&lt;/th&gt;
&lt;th&gt;Tavily advanced&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average score&lt;/td&gt;
&lt;td&gt;6.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7.8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;td&gt;5.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official doc in results&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8/8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;7/8&lt;/td&gt;
&lt;td&gt;5/8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official doc ranked first&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7/8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7/8&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2/8&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;td&gt;4/8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Firecrawl's ranking was as good as Exa's: the official page came first for 7 of 8 queries. Its score suffers because it returns whole pages, and on pages like Stripe's webhook docs the start of the markdown was bot-check and navigation text. Tavily ranked official pages lower, but its short excerpts were cut around the part of each page that matched the query. One query asked about &lt;code&gt;uuidv7&lt;/code&gt;, a PostgreSQL function that generates time-ordered IDs. Both Tavily modes returned the line from the official docs that shows how to call it, &lt;code&gt;uuidv7([shift interval])&lt;/code&gt;, which is what a coding agent needs to use the function correctly. Exa ranked the same page first, but its excerpt left that line out.&lt;/p&gt;

&lt;p&gt;DeepSeek was weakest on docs. For "verify Stripe webhook signature in Node.js", it returned only third-party npm packages and no Stripe docs, and for the &lt;code&gt;uuidv7&lt;/code&gt; query it returned mailing-list threads instead of the documentation page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Research and "find similar": Exa won all 8
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; open-ended discovery, where a good answer is a list of actual companies, papers or essays rather than articles about them.&lt;/p&gt;

&lt;p&gt;Research showed the widest gap in the test: Exa won all 8 queries and returned 33 distinct matching entities. DeepSeek was next with 23, and Firecrawl and both Tavily modes were between 11 and 14.&lt;/p&gt;

&lt;p&gt;For "open-source alternatives to Firecrawl for web scraping", Exa returned five GitHub repositories. Both Tavily modes and Firecrawl returned vendor listicles titled "Firecrawl alternatives". For "climate tech startups in Africa that raised funding in 2026", Exa named five startups with 2026 rounds. DeepSeek returned four copies of the same press release about a fund.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regional and non-English queries: Exa, with DeepSeek on Chinese
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; South African local queries in English, plus queries in Chinese, German, Spanish and Japanese.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;Tavily basic&lt;/th&gt;
&lt;th&gt;Tavily advanced&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average score&lt;/td&gt;
&lt;td&gt;5.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.1&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Results from the right region and language (of 40)&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DeepSeek won the Spanish query outright, and tied with Exa on the Chinese query about the latest AI regulation, with current official Chinese sources. Most other tools leaned on 2023 law-firm summaries. On the German query about a Deutsche Bahn strike, DeepSeek did worst, returning Swiss and Austrian pages that recycled 2024 strike stories. Exa was the only tool to make it clear that there was no current Deutsche Bahn strike.&lt;/p&gt;

&lt;p&gt;Every tool found local sources for the South African queries, and Exa and DeepSeek tied on Eskom's 2026 tariff increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search speed and size
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Median search time&lt;/th&gt;
&lt;th&gt;Text returned per search (median)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.83s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;35,000 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;1.48s&lt;/td&gt;
&lt;td&gt;12,800 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;1.50s, or 5.79s with page content&lt;/td&gt;
&lt;td&gt;68,000 characters with page content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily basic&lt;/td&gt;
&lt;td&gt;2.13s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5,600 characters&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily advanced&lt;/td&gt;
&lt;td&gt;3.30s&lt;/td&gt;
&lt;td&gt;8,400 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Payload size matters because an agent pays to read every token. Firecrawl's search returns the full page for each result. Those pages contained the right answer for all 8 current-facts queries, but at around five times Exa's volume, and one search that hit a PDF returned 2.4 million characters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching pages: Firecrawl returned the most complete markdown
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; give each tool a URL and check whether the returned markdown contains three to five exact sentences captured from the page in Chrome.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;th&gt;Tavily&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pages with usable content (of 40)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average share of key facts found&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;td&gt;41%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static pages&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10/10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10/10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10/10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript-heavy pages&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10/10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8/10&lt;/td&gt;
&lt;td&gt;6/10&lt;/td&gt;
&lt;td&gt;4/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10/10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;9/10&lt;/td&gt;
&lt;td&gt;0/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bot-protected pages&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8/10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5/10&lt;/td&gt;
&lt;td&gt;4/10&lt;/td&gt;
&lt;td&gt;3/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median time&lt;/td&gt;
&lt;td&gt;1.45s&lt;/td&gt;
&lt;td&gt;1.32s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.41s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.42s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Firecrawl got content from Zillow, G2, Glassdoor and Takealot, which Tavily failed outright. It refused two sites by policy: Instagram returned "we do not support this site", and a LinkedIn company page needs a separate enrichment setting turned on. The South African government site returned a server error to Firecrawl twice, while it loaded normally in a browser.&lt;/p&gt;

&lt;p&gt;Exa was the fastest by a wide margin, probably because it can serve pages from its own index rather than loading them live. Its index also caught it out once: for the 100-page GPT-4 technical report, it returned a citation record with the author list and references instead of the paper.&lt;/p&gt;

&lt;p&gt;DeepSeek returned a 404 for every PDF, which its docs list as the response for unsupported content types. On static pages it found 96% of the key facts, but its median fetch took 7.4 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crawling a docs site: Firecrawl vs Tavily
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; crawl a documentation site and return 25 pages. Exa and DeepSeek don't offer crawling.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;docs.astral.sh/uv&lt;/th&gt;
&lt;th&gt;docusaurus.io/docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;25/25 pages, all on the docs site, 19.5s&lt;/td&gt;
&lt;td&gt;25/25 pages, all on the docs site, 28.2s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily, depth 2&lt;/td&gt;
&lt;td&gt;23 to 24 pages per run, but only 4 on the docs site&lt;/td&gt;
&lt;td&gt;25 pages on 1 of 3 runs, nothing on the other 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily, "stay on this site"&lt;/td&gt;
&lt;td&gt;1 page, 3 of 3 runs&lt;/td&gt;
&lt;td&gt;0 pages, 3 of 3 runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tavily's crawl followed links off the site by default, into GitHub and the documentation theme's own site. Its &lt;code&gt;allow_external: false&lt;/code&gt; option, which should keep it on the site, returned at most one page in every run. Tavily's map endpoint found the right 20 to 25 URLs on both sites, so it can discover the pages, and the failure is in the crawl step.&lt;/p&gt;

&lt;p&gt;Each Firecrawl crawl cost 25 credits, and I only ran it once per site because the free credits had run out. Tavily's empty runs weren't billed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firecrawl, Tavily, Exa and DeepSeek pricing: is Firecrawl free?
&lt;/h2&gt;

&lt;p&gt;All four are free to start except DeepSeek. Prices below were checked on 23 October 2026, in US dollars.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Firecrawl&lt;/th&gt;
&lt;th&gt;Exa&lt;/th&gt;
&lt;th&gt;Tavily&lt;/th&gt;
&lt;th&gt;DeepSeek&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free plan&lt;/td&gt;
&lt;td&gt;1,000 credits a month, no card&lt;/td&gt;
&lt;td&gt;$10 credit a month, plus a one-time $10 for onboarding, no card&lt;/td&gt;
&lt;td&gt;1,000 credits a month, no card&lt;/td&gt;
&lt;td&gt;None. Minimum top-up $2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;2 credits per 10 results, plus 1 credit per scraped result&lt;/td&gt;
&lt;td&gt;$7 per 1,000 (auto), up to 10 results&lt;/td&gt;
&lt;td&gt;1 credit (basic) or 2 credits (advanced)&lt;/td&gt;
&lt;td&gt;$0.75 per 1,000, up to 50 results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetch&lt;/td&gt;
&lt;td&gt;1 credit per page, plus 1 per PDF page&lt;/td&gt;
&lt;td&gt;$1 per 1,000 pages&lt;/td&gt;
&lt;td&gt;1 credit per 5 URLs (basic)&lt;/td&gt;
&lt;td&gt;$0.75 per 1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid entry point&lt;/td&gt;
&lt;td&gt;Hobby: $19 a month for 5,000 credits&lt;/td&gt;
&lt;td&gt;Pay as you go&lt;/td&gt;
&lt;td&gt;$0.008 per credit, or Project at $30 a month for 4,000 credits&lt;/td&gt;
&lt;td&gt;Pay as you go&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Firecrawl charges per PDF page
&lt;/h3&gt;

&lt;p&gt;The free plan's 1,000 credits went much faster than I expected. The 194-page RFC 9110 PDF cost 194 credits on its own, and the GPT-4 technical report cost 100. The 10 PDFs used 564 credits, more than half the month's allowance. Firecrawl's billing docs do list this, as an extra credit per PDF page, but it's easy to miss when the headline is "1 credit per page". By the end of the test, Firecrawl had used 992 of its 1,000 free credits.&lt;/p&gt;

&lt;p&gt;The others used much less:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tavily:&lt;/strong&gt; 194 of its 1,000 credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exa:&lt;/strong&gt; $0.32 of $20.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek:&lt;/strong&gt; $0.05 of my $2 top-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What 1,000 searches costs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Cost per 1,000 searches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;$0.75, with page content included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exa&lt;/td&gt;
&lt;td&gt;$7 with up to 10 results; page text is billed at $1 per 1,000 pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tavily&lt;/td&gt;
&lt;td&gt;$8 (basic) or $16 (advanced), pay as you go&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firecrawl&lt;/td&gt;
&lt;td&gt;About $7.60 for links and descriptions only on Hobby; about $26.60 with 5 scraped pages, more if a result is a PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  DeepSeek Search API: what you need to know
&lt;/h2&gt;

&lt;p&gt;DeepSeek's Search and Fetch APIs launched on 17 October 2026. Each is a single &lt;code&gt;POST&lt;/code&gt; to &lt;code&gt;api.deepseek.com/v1/tools/search&lt;/code&gt; or &lt;code&gt;/v1/tools/fetch&lt;/code&gt; with your API key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search results carry more than the others' do.&lt;/strong&gt; Each result has a short snippet, a summary of around 600 to 800 characters, the full page if you ask for it, a publication date, an &lt;code&gt;authority&lt;/code&gt; score from 0 to 4 and a &lt;code&gt;rerank_score&lt;/code&gt; from 0 to 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price doesn't change with result count.&lt;/strong&gt; Asking for 50 results costs the same $0.75 per 1,000 as asking for 1. Failed and empty calls aren't billed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries are capped at 128 characters.&lt;/strong&gt; That's fine for keyword search, but too short for a long natural-language question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't de-duplicate URLs.&lt;/strong&gt; For "Firecrawl Hobby plan price", it returned Firecrawl's pricing page three times, each with a different affiliate code in the URL. Across 40 queries it returned 7 duplicates, the most of the four.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your data is stored in China.&lt;/strong&gt; DeepSeek's privacy policy says it stores personal data in the People's Republic of China, and that it shares search keywords with unnamed third-party search APIs. A &lt;a href="https://www.searchenginejournal.com/deepseek-opens-its-in-house-search-engine-to-developers/593526/" rel="noopener noreferrer"&gt;Search Engine Journal report&lt;/a&gt; notes that a provider called Bocha names itself as DeepSeek's web search supplier. Check this with your compliance team before sending customer queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using DeepSeek search inside Claude Code
&lt;/h3&gt;

&lt;p&gt;DeepSeek's docs say its search is free until 1 November when called through Claude Code, Codex or DeepSeek's own harness. That works by pointing Claude Code at DeepSeek's API, so DeepSeek's model replaces Claude in that session. I tried it first, and it isn't the same as using the API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code's WebSearch tool passed only titles and URLs to the model, without DeepSeek's summaries, page content or scores.&lt;/li&gt;
&lt;li&gt;WebFetch fetched the page itself rather than calling DeepSeek's Fetch API. Asked for a Paul Graham essay verbatim, it returned a summary instead, saying quotations were limited to 125 characters.&lt;/li&gt;
&lt;li&gt;The search calls were free, but the model's tokens weren't, and a new account has no starting balance. My first attempt failed with &lt;code&gt;402 Insufficient Balance&lt;/code&gt; until I topped up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want DeepSeek's results in an agent, call the API directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using them with AI agents
&lt;/h2&gt;

&lt;p&gt;All four publish an &lt;code&gt;llms.txt&lt;/code&gt; file for coding agents to read. Firecrawl, Exa and Tavily each have an official MCP server that covers search and fetch. Firecrawl's and Tavily's servers also cover crawling. I couldn't find an MCP server from DeepSeek. For deeper agent setup testing of two of these tools, see &lt;a href="https://techstackups.com/comparisons/firecrawl-vs-tavily/" rel="noopener noreferrer"&gt;Firecrawl vs Tavily&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which should you use?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search for an AI agent:&lt;/strong&gt; Exa. It won on current facts, news, research and non-English queries, and its results are compact enough to put straight into context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading specific URLs, PDFs or protected pages:&lt;/strong&gt; Firecrawl. It returned usable content from the most pages and handled every PDF. Budget for PDFs, which cost a credit per page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawling a whole site:&lt;/strong&gt; Firecrawl. Tavily's crawl wandered off-site or came back empty in our runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest cost at high volume:&lt;/strong&gt; DeepSeek, if your queries are public data and data stored in China is acceptable. It was fast and fresh on news, but weaker on docs lookups and it can't fetch PDFs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smallest, cheapest results to feed a model:&lt;/strong&gt; Tavily. Its basic search returned about 5,600 characters and did well on docs lookups, though it didn't win any category outright in our tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much does Firecrawl cost?
&lt;/h3&gt;

&lt;p&gt;Firecrawl's free plan gives you 1,000 credits a month without a card, and Hobby costs $19 a month for 5,000 credits. A search costs 2 credits per 10 results, a page costs 1 credit, and each PDF page costs another credit, so long PDFs use credits fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a free web search API?
&lt;/h3&gt;

&lt;p&gt;Three of the four have free plans with no card: Tavily gives 1,000 credits a month, Firecrawl 1,000 credits a month, and Exa $10 of credit a month. DeepSeek has no free tier for direct API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the best alternatives to Firecrawl?
&lt;/h3&gt;

&lt;p&gt;For search, Exa returned better results in our tests, and DeepSeek is much cheaper. For turning pages into markdown, none of the alternatives we tested matched Firecrawl: Tavily found 74% of the key facts, Exa 69% and DeepSeek 41%, against Firecrawl's 85%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Tavily search better than Exa search?
&lt;/h3&gt;

&lt;p&gt;No. Exa produced the best results for 31 of our 40 queries, against 7 for Tavily basic and 5 for Tavily advanced, and it scored higher on every type of query. Tavily came closest on docs lookups, where it averaged 7.5 against Exa's 7.8.&lt;/p&gt;

</description>
      <category>deepseek</category>
      <category>firecrawl</category>
      <category>todayisearched</category>
    </item>
    <item>
      <title>Gemini vs ElevenLabs Voice Cloning and TTS (2026): Gemini 3.8 Flash vs Eleven v4</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 14:02:55 +0000</pubDate>
      <link>https://dev.to/ritza/gemini-vs-elevenlabs-voice-cloning-and-tts-2026-gemini-38-flash-vs-eleven-v4-4p2n</link>
      <guid>https://dev.to/ritza/gemini-vs-elevenlabs-voice-cloning-and-tts-2026-gemini-38-flash-vs-eleven-v4-4p2n</guid>
      <description>&lt;p&gt;Google released Gemini 3.8 Flash TTS on 23 September 2026. ElevenLabs released Eleven v4 five days later. I put both through the same five tests, covering emotional tags, hard-to-say text, long-form narration, a two-person argument, and cloning my own voice.&lt;/p&gt;

&lt;p&gt;ElevenLabs won three of the five (hard text, long-form, dialogue). Gemini won two: emotion, and voice cloning, where it kept my South African accent and ElevenLabs gave me a British one. If you came here for the ElevenLabs voice cloning question specifically, that result is the interesting one, and it's further down.&lt;/p&gt;

&lt;p&gt;Google ships a "Flash TTS" and a "Flash-Lite TTS" under the same 3.8 label, which is confusing. I tested Flash, and Eleven v4 on the ElevenLabs side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini vs ElevenLabs: the quick verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;One-line reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Voice cloning (clean sample, also checked by a friend)&lt;/td&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;Kept my accent and tone; ElevenLabs laid my voice over a heavy British accent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice cloning (noisy sample)&lt;/td&gt;
&lt;td&gt;No winner&lt;/td&gt;
&lt;td&gt;Gemini rejected the sample; ElevenLabs accepted it and the clone was much worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expressive tags and emotion&lt;/td&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;Smoother shifts, pauses that follow context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard text (numbers, names)&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Got the order number, the date and the Irish name right; Gemini got the phone number right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-form narration&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Consistent throughout; Gemini failed on day one and dropped in tone on day two&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-speaker dialogue&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Better interruption, and emotion used inside the sentence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price&lt;/td&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;About $0.0135 vs $0.02 per minute today, and about $0.027 vs $0.08 once both promos end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How we tested
&lt;/h2&gt;

&lt;p&gt;This is one person listening. I tested in English only, and I ran each scenario once with default settings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same text, same direction.&lt;/strong&gt; Both models got identical text. Direction went inline, sentence by sentence (&lt;code&gt;[whispering]&lt;/code&gt; for ElevenLabs, &lt;code&gt;&amp;lt;whispering&amp;gt;&lt;/code&gt; for Gemini). I left Gemini's separate Style instructions box untouched so the comparison is like for like. (My first Gemini attempt used one overall style line and came out hushed throughout, so I threw it out as a test-design flaw.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-tier web UIs&lt;/strong&gt; for everything except cloning: ElevenLabs with v4 selected, and Google AI Studio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One caveat in ElevenLabs' favour.&lt;/strong&gt; It returns two outputs per prompt and I picked the better one. Gemini returns one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloning ran through the APIs&lt;/strong&gt;, because AI Studio wouldn't run cloning without billing and instant voice cloning on ElevenLabs needs a paid plan in the UI. I cloned my own voice from the same 29.5 second sample on both. A friend listened to the cloning results too, so that verdict isn't mine alone. The other four are mine only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voices.&lt;/strong&gt; Scenarios 1 to 3 used Gemini's Cleo (warm and engaging) against ElevenLabs' Lauren (friendly, comforting and soft). The dialogue test added Mako and Jack John.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tested in early October 2026. Scroll to the end to try a blind A/B on the clips and disagree with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini voice cloning vs ElevenLabs voice cloning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; can the model sound like a specific person from about 30 seconds of audio, and keep that identity when the line changes from neutral to emotional to narration. I'm South African, which makes accent drift easy to hear.&lt;/p&gt;

&lt;p&gt;Gemini asks for a 10 to 30 second sample plus a spoken consent recording, and the consent has to come from the same voice as the sample. ElevenLabs takes just the sample. Creating the clean clone took 5.54 seconds on Gemini and 3.23 on ElevenLabs.&lt;/p&gt;

&lt;p&gt;Here are the three clean-sample lines, same text on both:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen: Cloned voice: neutral line&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clean 29.5 s sample of my own voice&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-gemini-clean-neutral.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-elevenlabs-clean-neutral.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Listen: Cloned voice: emotional line&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does the clone keep its identity when the delivery changes?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-gemini-clean-emotional.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-elevenlabs-clean-emotional.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Listen: Cloned voice: narration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Longer read from the same clones&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-gemini-clean-narration.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-elevenlabs-clean-narration.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemini wins this one clearly. Its clones kept my accent and tone far better. The ElevenLabs clones kept a slight resemblance to my voice, but it sounded like my voice and tone had been overlaid on someone else's accent, a heavily British one. That held across all three lines, so it looks like a general problem with ElevenLabs cloning on my sample. Neither model captured expression well from a 30 second sample, though Gemini was less bad, and its emotional line didn't convey the emotion accurately. The Gemini narration clip had the best voice and accent match of anything I tested, but it sounded damp and fuzzy next to Gemini's other clips, like a recording of me.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens with a noisy sample
&lt;/h3&gt;

&lt;p&gt;I also recorded the same text in a noisier setting. Gemini rejected it outright at clone creation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Voice mismatch detected. The speaker in the consent audio does not match the speaker in the voice sample.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My consent clip was recorded in a quiet room, so Gemini was comparing a quiet recording against a noisy one and decided they weren't the same person. That's a useful safeguard and an annoying one if your only sample is noisy.&lt;/p&gt;

&lt;p&gt;ElevenLabs accepted the same noisy sample with no consent step. The clone was much worse than its clean one: a very slight resemblance, and bad accent and expression. Here's the noisy narration, ElevenLabs only, since Gemini produced nothing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/clone-elevenlabs-noisy-narration.mp3" rel="noopener noreferrer"&gt;Listen: ElevenLabs noisy-sample narration — MP3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only clone a voice you own or have permission to use. Gemini enforces consent at this step and ElevenLabs doesn't, which matters if you're building a product that lets users clone voices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expressive tags and emotion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; does the model perform the whisper, the laugh and the sigh, or just read the word, and how smooth is the shift between them. The script runs from a whisper to excitement to a laugh to a voice cracking with emotion.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gemini&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tag syntax&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;laugh&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;short pause&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;sigh&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[whispering]&lt;/code&gt;, &lt;code&gt;[laughs]&lt;/code&gt;, &lt;code&gt;[excited, fast]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emotion tags&lt;/td&gt;
&lt;td&gt;I used the same words as ElevenLabs (&lt;code&gt;&amp;lt;whispering&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;excited&amp;gt;&lt;/code&gt;); no problems recorded&lt;/td&gt;
&lt;td&gt;Free-form, stackable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation time&lt;/td&gt;
&lt;td&gt;14.5 s&lt;/td&gt;
&lt;td&gt;9.87 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Listen: Emotion and tag-following&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whisper, excitement, laugh, sigh, voice cracking, with the same direction on every sentence&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s1-emotion-gemini.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s1-emotion-elevenlabs.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemini sounded significantly more natural. It inferred pauses from context, and the pauses sound like someone talking to a person and waiting for their reaction. My favourite moment is at 0:16. The line is "We're giving you the Lisbon project," and Gemini went from a whisper to a slight exclamation with no emotion tag at all, purely because it understood the news was exciting. The worst moment was ElevenLabs at 0:18, where the shift from &lt;code&gt;laugh&lt;/code&gt; to &lt;code&gt;whispering&lt;/code&gt; is jarring.&lt;/p&gt;

&lt;p&gt;My read on why: ElevenLabs seems to treat the emotion tag as a parameter and then read that sentence with it. Gemini seems to read the current sentence together with the next one, so the emotion spans both. That is my impression from listening. I didn't hear any tag spoken aloud in this test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen: Two-speaker argument with an interruption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Distinct voices, an em-dash cut-off, and a [laughs, then quietly] tag&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s5-two-speaker-gemini.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s5-two-speaker-elevenlabs.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs won the dialogue test by a distance. On Gemini the interruption feels like a pause, and the emotion lands as a pre-sentence emote. For the &lt;code&gt;[laughs, then quietly]&lt;/code&gt; tag, Gemini produced a normal laugh and then kept speaking loudly, ignoring "then quietly". ElevenLabs produced a laugh that fit the conversation. At 0:12 it also inferred the severity of the emotion from context, which it did well in every scenario. Neither is perfect, and I flagged robotic glitches and some overacting. Generation took 14.55 s on Gemini and 6.26 s on ElevenLabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers, names and hard text
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; the stuff that breaks customer-facing voice. An order number, a date, a currency amount, a phone number, a URL, and a run of hard names. No tags on either side.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Gemini&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A-7419-X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Skipped the dash after the A, which matters when someone is searching an order number&lt;/td&gt;
&lt;td&gt;Fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3 April 2026&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read it as "three April," which sounds unnatural&lt;/td&gt;
&lt;td&gt;"April third," correct and far more natural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0800 555 0147&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Better: "0 eight hundred"&lt;/td&gt;
&lt;td&gt;Read every zero: "0 eight 0 0"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Wojciechowski&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Polish &lt;code&gt;w&lt;/code&gt;, not the &lt;code&gt;ch&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Polish &lt;code&gt;ch&lt;/code&gt;, not the &lt;code&gt;w&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Siobhan Ní Bhriain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Got Siobhan, read Bhriain as spelt&lt;/td&gt;
&lt;td&gt;Whole name in native Irish pronunciation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dr. Nkosi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clear, distinct from the name&lt;/td&gt;
&lt;td&gt;Correct, but the "r" nearly merges into Nkosi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rest were fine on both: $1,249.50, R22,800, acme-labs.io/returns, 14:45, "Thursday the 9th," HTTP 404, 2.4 GHz, Xiaomi, Worcestershire, Leicester and Edinburgh. I'm not giving a score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen: Hard-to-say content&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Order numbers, dates, currencies, phone numbers, URLs and difficult names&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s2-hard-to-say-gemini.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s2-hard-to-say-elevenlabs.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs wins, mostly on the dropped dash in the order number and the Irish name. But remember the two-takes caveat: ElevenLabs gave me two outputs and I chose the better one. Generation took 18.88 s on Gemini and 17.53 s on ElevenLabs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long-form narration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; about 2.5 minutes of Dickens (the opening of &lt;em&gt;Great Expectations&lt;/em&gt;), plain text with no tags, to see whether pace creeps, the voice changes character, or the tone jumps.&lt;/p&gt;

&lt;p&gt;ElevenLabs handled it without trouble. No pace creep, the voice kept its character, the paragraph breaks sounded intentional, and there were no sudden tone or loudness jumps.&lt;/p&gt;

&lt;p&gt;Gemini failed on my first day of testing. It would speak only the first four to six words of the passage and then fail, repeatedly. I lost several retries to it.&lt;/p&gt;

&lt;p&gt;I retried the next day and the full passage generated. I don't know why it failed the first time. But even the successful run has a problem: between 2:06 and 2:08 the tone drops jarringly, in a way that doesn't match the emotion the surrounding text calls for. Generation took 45.9 s on Gemini and 48.28 s on ElevenLabs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen: Long-form narration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dickens, ~2.5 minutes. Listen to the start, then jump to the last 20 seconds.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s3-long-form-gemini.mp3" rel="noopener noreferrer"&gt;Gemini (gemini-3.8-flash-tts) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techstackups.com/img/comparisons/gemini-vs-elevenlabs/s3-long-form-elevenlabs.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs was the better read of context here. I was impressed at how well it inferred expression from the text alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which is the best TTS model? Which is the best voice cloning model?
&lt;/h2&gt;

&lt;p&gt;This covers two models. For the 16-provider view, see &lt;a href="https://techstackups.com/comparisons/best-tts-2026-blind-benchmark/" rel="noopener noreferrer"&gt;Best TTS in 2026: blind benchmark&lt;/a&gt;, and for the realtime and price angle see &lt;a href="https://techstackups.com/comparisons/cartesia-vs-elevenlabs/" rel="noopener noreferrer"&gt;Cartesia vs ElevenLabs&lt;/a&gt;. Within these two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Creators and audiobooks:&lt;/strong&gt; ElevenLabs. It stayed consistent across 2.5 minutes of narration and Gemini didn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer-facing voice and IVR:&lt;/strong&gt; ElevenLabs, with caveats. Gemini's phone-number read was better, but the dropped dash in an order number is the kind of error that costs real money.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers watching API cost:&lt;/strong&gt; Gemini, by a wide margin, if its long-form reliability holds up for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice cloning:&lt;/strong&gt; Gemini, at least for an accent like mine and a clean sample. It also enforces consent. ElevenLabs may do better with other voices, but I only cloned one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dialogue and character voices:&lt;/strong&gt; ElevenLabs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expressive reads where you want the model to infer emotion:&lt;/strong&gt; Gemini.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself: blind A/B
&lt;/h2&gt;

&lt;p&gt;Listen without knowing which is which, vote, and then see my verdict next to yours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/#try-it-yourself-blind-ab" rel="noopener noreferrer"&gt;Try the interactive blind A/B test on Techstackups&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  My recommendation
&lt;/h2&gt;

&lt;p&gt;I'd use ElevenLabs for anything that has to be right first time: narration, dialogue, and text full of numbers. I'd use Gemini when cost matters, when I want a model that reads emotion across sentences, or when I need a clone of a real voice that keeps its accent. If you take one thing from this, take the cloning result. On a clean 30 second sample, Gemini kept me sounding like me while ElevenLabs didn't. Test with your own voice before you commit.&lt;/p&gt;

&lt;p&gt;I didn't test voice design from a text description, re-roll consistency, other languages, or the UI friction of each tool.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gemini</category>
      <category>elevenlabs</category>
      <category>tts</category>
    </item>
    <item>
      <title>Monospace from Directus Review: An AX Audit</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 13:06:05 +0000</pubDate>
      <link>https://dev.to/ritza/monospace-from-directus-review-an-ax-audit-53aa</link>
      <guid>https://dev.to/ritza/monospace-from-directus-review-an-ax-audit-53aa</guid>
      <description>&lt;p&gt;&lt;a href="https://monospace.io" rel="noopener noreferrer"&gt;Monospace&lt;/a&gt; is a new product for giving AI agents safe access to company data, from Monospace Inc., the company behind &lt;a href="https://directus.io" rel="noopener noreferrer"&gt;Directus&lt;/a&gt;. In our tests, a coding agent connected to it over MCP and answered questions about our data correctly. But the agent only found Monospace when we searched for its tagline, and for Monospace's own use case it recommended other tools, including Directus.&lt;/p&gt;

&lt;p&gt;Four days after launch, we put Monospace through our &lt;a href="https://techstackups.com/articles/what-is-agent-experience-and-why-should-you-care/" rel="noopener noreferrer"&gt;agent experience (AX)&lt;/a&gt; audit, following our &lt;a href="https://techstackups.com/articles/how-to-do-an-ax-audit/" rel="noopener noreferrer"&gt;AX audit method&lt;/a&gt;. The audit has four stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness:&lt;/strong&gt; does the model know Monospace without searching?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability:&lt;/strong&gt; can an agent find Monospace when it's looking for a tool like it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation:&lt;/strong&gt; does the agent speak well of Monospace, and recommend it over other options?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration:&lt;/strong&gt; can the agent build a working proof of concept with Monospace?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How we tested&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All testing used Claude Opus 5.5 (&lt;code&gt;claude-opus-5-5&lt;/code&gt;) in Claude Code 2.1.285, on 6 October 2026. Every session started in a fresh, empty folder with no project files, memory, MCP servers, skills or plugins. Awareness questions ran with no tools at all, and discoverability and reputation questions with web search and web fetch only. We ran each prompt once, so treat these results as one developer's experience rather than a benchmark.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scores at a glance
&lt;/h2&gt;

&lt;p&gt;Agents don't know Monospace, and only find it when they search for its own tagline. Once they do find it, they like the design but hedge every recommendation, and often fill gaps with facts about Directus. In the integration, the agent installed Monospace, loaded our data and answered questions over MCP with a few short human steps, but the free plan blocks the governance features Monospace leads with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness: FAIL (1 / 4)&lt;/strong&gt; — Unknown from memory; the name only brings up the Directus company&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability: POOR (2 / 4)&lt;/strong&gt; — Found only by searching its own tagline, and not even then in one of three tests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation: POOR (2 / 4)&lt;/strong&gt; — Liked, never recommended outright; facts often wrong or borrowed from Directus&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration: OK (3 / 4)&lt;/strong&gt; — Working MCP integration with correct answers; field-level permissions blocked on the free plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overall: POOR (2 / 4)&lt;/strong&gt; — Works once an agent knows it exists; getting there is the hard part&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See our &lt;a href="https://techstackups.com/articles/how-to-do-an-ax-audit/" rel="noopener noreferrer"&gt;AX audit rubric&lt;/a&gt; for how we score each stage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Awareness: does the model know Monospace?
&lt;/h2&gt;

&lt;p&gt;Awareness tests what a model knows from training data alone, with no web search. For a product launched four days earlier, we expected the answer to be no. What matters is how the model fails, whether it admits it doesn't know, makes something up, or confuses Monospace with something else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAIL (1 / 4)&lt;/strong&gt; — Unknown from memory; the name only brings up the Directus company&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Unknown, or confused with something else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Recognises the name, details vague or invented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Accurate description from memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Accurate, and recommended unprompted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We started with two questions that describe Monospace's pitch without naming it. Each answer below comes from a fresh session with no tools.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvl6dm6rqj32uhbm4k1z6.png" alt="Awareness · Monospace not named" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Far3g41t76g8aonuiubes.png" alt="Awareness · Monospace not named" width="799" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first answer describes the gap Monospace says it fills, and the second picks a competitor with confidence. Then we named Monospace:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffu588tu589rjegpj40iw.png" alt="Awareness · named, with its domain" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfrbnec4ifbg1tmldyj0.png" alt="Awareness · named, with its tagline" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F82p7ofmiaqjql17h1bh3.png" alt="Awareness · asking for a recommendation" width="798" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given the domain, the model knew the company but not the product. Given the tagline, it said it didn't know. It never invented features, which is good behaviour, but a developer asking their agent about Monospace today either hears about Directus or gets "I don't know". In other sessions the agent also raised two name clashes, monospace fonts and Google's old internal "Monospace" codename for what became Project IDX.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness: FAIL (1 / 4)&lt;/strong&gt; — Unknown from memory; the name only brings up the Directus company&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Monospace can do about it
&lt;/h3&gt;

&lt;p&gt;Training data catches up slowly, so there's no quick fix here. The useful work is making sure the next training cut sees Monospace as a product in its own right. That means launch posts, guides and comparisons that pair the name with what it does ("Monospace, the governed API layer from Directus").&lt;/p&gt;




&lt;h2&gt;
  
  
  Discoverability: can an agent find Monospace?
&lt;/h2&gt;

&lt;p&gt;Discoverability tests whether an agent with web search finds the product when it's looking for something like it. We ran three six-question "funnels" in a single session each. Each funnel starts with a broad problem and narrows towards Monospace one question at a time, then names it at question 6. We recorded the first question where Monospace appeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POOR (2 / 4)&lt;/strong&gt; — Found only by searching its own tagline&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Only appears when named&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Only appears with its own positioning language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Appears for a specific description of the problem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Top choice for broad problem descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each funnel asks from a different angle, because different people describe the same problem differently.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Funnel&lt;/th&gt;
&lt;th&gt;Who's asking&lt;/th&gt;
&lt;th&gt;First question&lt;/th&gt;
&lt;th&gt;Where Monospace first appeared&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A team wanting agents to use company data&lt;/td&gt;
&lt;td&gt;"My company wants to let AI agents work with our internal data…"&lt;/td&gt;
&lt;td&gt;Question 5, which used Monospace's own tagline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;A backend developer&lt;/td&gt;
&lt;td&gt;"What's the best way to put one API in front of several databases…"&lt;/td&gt;
&lt;td&gt;Question 6, only when we named it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Someone building agents&lt;/td&gt;
&lt;td&gt;"I'm ending up with a separate MCP server for every system we have…"&lt;/td&gt;
&lt;td&gt;Question 5, which used Monospace's own tagline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here are funnel A's six questions, asked in one session, with the start of each answer.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqv2fnpfij0neqr10vn2.png" alt="Discovery funnel step 1" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbh7joqkvtdqu2xa7ha0j.png" alt="Discovery funnel step 2" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkhqqjer69cevirle91ko.png" alt="Discovery funnel step 3" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfs0jvjlk90q5mmrwcju.png" alt="Discovery funnel step 4" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv5npa0rzwigjr59412g0.png" alt="Discovery funnel step 5" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk87pzog0k5vecc4uios3.png" alt="Discovery funnel step 6" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At question 3, the agent searched the web for a product that does what Monospace does and concluded that none exists. In funnel C it said the same thing in different words: "No product does this as a drop-in layer, and a gateway can't do it reliably."&lt;/p&gt;

&lt;p&gt;Funnel B, the developer's angle, went worse. At question 3, the agent recommended Monospace's parent company's older product for Monospace's job:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxch5z9cq5qeahqd1z19.png" alt="Discoverability · funnel B, question 3" width="799" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And searching for Monospace's own tagline didn't find it either:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc1qlglu6v1v06gdpan6h.png" alt="Discoverability · funnel B, question 5" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once found, Monospace became the agent's top pick in all three funnels. It called Monospace "the closest match to your requirements of anything I've found" in funnel A, and "the best conceptual fit of everything we've looked at" in C. In B it said "the product is brand new but the team isn't." Each time it added the same caveat, that Monospace was days old and unproven.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability: POOR (2 / 4)&lt;/strong&gt; — Found only by searching its own tagline, and not even then in one of three tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Monospace can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Publish pages built around the problems agents are asked about
&lt;/h4&gt;

&lt;p&gt;Agents ran searches like &lt;code&gt;MCP gateway agent identity fine-grained authorization audit log Postgres SaaS 2026&lt;/code&gt; and &lt;code&gt;AI agent data access control plane field-level permissions MCP new product 2026&lt;/code&gt;, and none of them surfaced Monospace. Guides and docs titled around those problems, in a developer's words, would give agents something to find.&lt;/p&gt;

&lt;h4&gt;
  
  
  Get into the comparison articles agents already read
&lt;/h4&gt;

&lt;p&gt;The recommendations agents made instead came from vendor comparison posts and round-ups, Bytebase and Cerbos blog posts, Product Hunt, and MCP directories like Glama. That's where Hasura, WunderGraph Cosmo, DreamFactory and Cerbos got their mentions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Separate Monospace from Directus on purpose
&lt;/h4&gt;

&lt;p&gt;In funnel B the agent recommended Directus for Monospace's use case. A clear "Monospace vs Directus: which should I use?" page on both sites would teach agents when to send developers to which product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reputation: do agents recommend Monospace?
&lt;/h2&gt;

&lt;p&gt;Reputation tests what the agent thinks once Monospace is named, including whether it recommends it over competitors and whether it gets the facts right. We ran eight questions, each in a fresh session with web search. Hasura and WunderGraph Cosmo were among the competitors the agent named most often before it found Monospace. We added Directus on purpose, because of funnel B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POOR (2 / 4)&lt;/strong&gt; — Liked, never recommended outright; facts often wrong&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Not recommended, or core facts wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Heavy caveats, facts unreliable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Recommended for specific scenarios, facts mostly right&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Recommended over alternatives, facts right&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What do you think of Monospace for governed agent access?&lt;/td&gt;
&lt;td&gt;"Worth a proof of concept, but I wouldn't put it in production yet."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace over Hasura?&lt;/td&gt;
&lt;td&gt;No: "use Hasura for production now, and run a small trial of Monospace alongside it"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace over WunderGraph Cosmo?&lt;/td&gt;
&lt;td&gt;Yes, conditionally: "I'd lean towards Monospace for what you described"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace over Directus?&lt;/td&gt;
&lt;td&gt;Yes, conditionally: "mainly because you need internal APIs as well as Postgres"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20-person startup on Postgres, Stripe and HubSpot&lt;/td&gt;
&lt;td&gt;"Not yet. It's aimed at your exact problem, but two of your three systems aren't supported today."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regulated company needing self-hosting, SSO and a full audit trail&lt;/td&gt;
&lt;td&gt;"Yes, it's worth shortlisting, but don't approve it yet."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main risks of adopting Monospace (name only)&lt;/td&gt;
&lt;td&gt;Couldn't identify it, and answered about a font&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing and licensing vs Hasura and Cosmo (name only)&lt;/td&gt;
&lt;td&gt;Priced Directus instead, "fairly confident (~75%)"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Whenever the agent found Monospace, it liked the design. For a regulated company, it called it "the right design for a regulated environment: agents never hold database credentials directly." But it never recommended Monospace without conditions, and most verdicts came back to how new it is.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fig77y9ljrl5ntua73r3p.png" alt="Reputation · small-team scenario" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fevbnk3obnr20nxb6bp6i.png" alt="Reputation · Monospace vs Hasura" width="798" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answers' quality depended on which pages the agent reached. The most accurate answer, the startup "Not yet", came from Monospace's own homepage, pricing page and connectors page. It found that Stripe and HubSpot are listed as "Coming Soon", and that row/column-level security and audit logs aren't on the free plan. When the agent read less, it filled the gaps with outdated or borrowed information:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the agent said&lt;/th&gt;
&lt;th&gt;What's true&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monospace's pricing isn't on the site&lt;/td&gt;
&lt;td&gt;It's at &lt;a href="https://monospace.io/pricing" rel="noopener noreferrer"&gt;monospace.io/pricing&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace has a GraphQL API&lt;/td&gt;
&lt;td&gt;No GraphQL API is documented. It offers REST, an SDK and MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Directus's licence is "BSL"&lt;/td&gt;
&lt;td&gt;Directus moved to its own MSCL licence in 2026, and that licence is Directus's, not Monospace's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monospace costs $499/month on a Team plan&lt;/td&gt;
&lt;td&gt;That's Directus Cloud pricing. Monospace has a free Starter plan, then custom-priced Business and Enterprise plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The product page never names Postgres"&lt;/td&gt;
&lt;td&gt;True. Postgres support is only listed on the connectors page and in the docs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two questions that named Monospace without its domain went worst:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fewxs7ujgn8mqlkrxi3do.png" alt="Reputation · risks, name only" width="799" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6u2sa7dh9h8oek8fwwz.png" alt="Reputation · pricing, name only" width="799" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the risks question, the agent searched for "Monospace developer tool" and "Monospace launch 2026", and neither search found the product. The pricing comparison never reached monospace.io.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monospace vs Directus
&lt;/h3&gt;

&lt;p&gt;Directus is Monospace Inc.'s older product, and it has its own MCP server. When we asked whether to choose Monospace over Directus for exposing Postgres and internal APIs to apps and agents, it leaned towards Monospace, "mainly because you need internal APIs as well as Postgres". Its reasoning was that Directus is built around a single SQL database, while Monospace applies one permissions model across several sources. It described the two as "a sibling product, not an unknown vendor", and still pointed teams that only need Postgres towards Directus, for its proven field-level permissions and its admin UI.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reputation: POOR (2 / 4)&lt;/strong&gt; — Liked, never recommended outright; facts often wrong or borrowed from Directus&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Monospace can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Put the key facts on the pages agents read first
&lt;/h4&gt;

&lt;p&gt;The homepage was the page agents read most, and it doesn't name Postgres or state a licence. Agents that stopped there guessed both. A line on the homepage naming the supported databases, the licence and where pricing lives would have fixed several wrong answers.&lt;/p&gt;

&lt;h4&gt;
  
  
  Settle "public preview" or "GA"
&lt;/h4&gt;

&lt;p&gt;Monospace's launch article calls it a public preview, and the Directus community announcement calls it GA. Agents picked up both ("Their own posts disagree on whether it's "GA" or "public preview""), and used "preview" as a reason not to recommend Monospace for production. One status everywhere would remove that reason.&lt;/p&gt;

&lt;h4&gt;
  
  
  Publish what agents told our test user to ask sales
&lt;/h4&gt;

&lt;p&gt;For the regulated company, the agent listed five questions to ask Monospace's sales team. They covered whether audit logs record the full query, whether the log is tamper-evident and can stream to a SIEM, whether SAML or OIDC work self-hosted, what licence covers self-hosting, and what compliance reports exist. Docs pages that answer those would give agents what they need to move past "don't approve it yet".&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration: can an agent build with Monospace?
&lt;/h2&gt;

&lt;p&gt;Integration tests whether an agent can install the product and build a "hello world" with as little human help as possible. We gave it three spreadsheets for a fictional business (50 customers, 30 products, 300 orders with 741 line items) and asked it to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up Monospace and give us a localhost link.&lt;/li&gt;
&lt;li&gt;Get the spreadsheet data into Monospace and connect itself to it.&lt;/li&gt;
&lt;li&gt;Answer a question using Monospace: which region had the most revenue in the last three months, and who were its top three customers?&lt;/li&gt;
&lt;li&gt;Create a restricted key for an "analyst" agent that can't see customer emails, and prove it.&lt;/li&gt;
&lt;li&gt;Mark one order as refunded through Monospace.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We ran it in Claude Code's default permission mode, approving each command, so we could stop the agent from reading anything outside its folder. Monospace has no hosted signup ("Get Started" leads to a contact form), so the agent had to self-host it with Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OK (3 / 4)&lt;/strong&gt; — Working MCP integration; field-level permissions blocked on the free plan&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; No working hello world&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Works, but needed several human steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Works with short human steps and minor workarounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; End to end, governance included, at most one human step&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing: one hint, then four minutes
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhuadjccaedxd1no42ptg.png" alt="Integration · step 1, install" width="798" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We replied "It's the one at monospace.io.", and the agent then read the installation page, downloaded the official Docker Compose file, and had Monospace v1.0.0 running with Postgres, RabbitMQ and Redis in 4 minutes 12 seconds, with no errors.&lt;/p&gt;

&lt;p&gt;Creating the admin account, accepting the licence agreement and creating a workspace took us 6 and a half minutes in the browser, with no email verification. The last onboarding screen shows how to connect Claude Code (a &lt;code&gt;claude mcp add&lt;/code&gt; command) and how to install Monospace's agent skills plugin.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftechstackups.com%2Fimg%2Farticles%2Fmonospace-ax-audit%2Fintegration-i1-connect-screen.png" alt="Monospace onboarding screen titled Connect to your workspace, showing a claude mcp add command and commands to install the Monospace agent skills plugin" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading the data and connecting over MCP
&lt;/h3&gt;

&lt;p&gt;Monospace's homepage illustrates its data sources as files like &lt;code&gt;customers.xlsx&lt;/code&gt; and &lt;code&gt;inventory.xlsx&lt;/code&gt;, but its connectors are PostgreSQL, MySQL and MariaDB, with no spreadsheet connector. The agent worked this out from the docs and took a sensible route. It converted the spreadsheets to SQL, loaded them into a new database inside Monospace's own Postgres container, and registered that database as a data source through Monospace's API. Monospace detected the relationships between the tables on its own, and the agent checked that every table's row count matched the spreadsheets.&lt;/p&gt;

&lt;p&gt;The agent leaned heavily on Monospace's agent-friendly docs, including &lt;code&gt;llms.txt&lt;/code&gt;, the 1.4 MB &lt;code&gt;llms-full.txt&lt;/code&gt;, and raw Markdown versions of each docs page. The agent said the OpenAPI spec served by the running instance "mattered most". The docs have no request examples for creating a data source, so it worked out the format from that spec, by trial and error.&lt;/p&gt;

&lt;p&gt;It then needed one thing it couldn't do itself, an API key. It asked us to create one in the UI and write it to a file with a &lt;code&gt;! pbpaste&lt;/code&gt; command, so the key never appeared in the chat. With a key in place, the agent tested Monospace's MCP server by hand with &lt;code&gt;curl&lt;/code&gt; and found 7 tools. It then wrote a project-level &lt;code&gt;.mcp.json&lt;/code&gt; that reads the key from the file at runtime, so the key isn't stored in the config either. Claude Code needed a restart to load the new MCP server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hello world: a correct answer
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2tz0zercu88ukwnfeltq.png" alt="Integration · step 3, hello world" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Top customers in North&lt;/th&gt;
&lt;th&gt;Revenue (paid and shipped orders)&lt;/th&gt;
&lt;th&gt;Orders&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Omar Patel&lt;/td&gt;
&lt;td&gt;$19,744.22&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mateo Patel&lt;/td&gt;
&lt;td&gt;$15,575.57&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sipho Dube&lt;/td&gt;
&lt;td&gt;$13,420.81&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every figure matched what we calculated from the spreadsheets beforehand. The agent stated its date window and which order statuses it counted, showed how the ranking changes if pending orders are included, and cross-checked the result in Postgres.&lt;/p&gt;

&lt;p&gt;Monospace can't calculate totals itself (its docs have a page called "No Aggregates"), so the agent pulled the rows through MCP and added them up in a Python script. That's fine for 300 orders, but a bigger dataset would mean moving a lot of rows through the agent's context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Field-level permissions: blocked by the free plan
&lt;/h3&gt;

&lt;p&gt;Asked for a restricted "analyst" key that can't see customer emails, the agent read the access control docs, built a policy, and tried to create it. Monospace refused, and the agent queried Monospace's licence API to find out why:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtts45x9nkx5uml71l49.png" alt="Integration · step 4, restricted key" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Starter licence limit (from &lt;code&gt;/api/system/license&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom policies (needed to hide a field)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom roles&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service accounts (needed for a separate agent identity)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspaces&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seats&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It confirmed nothing had been half-created, and declined to fake the result by hiding the column in Postgres instead, saying that "isn't what you asked for".&lt;/p&gt;

&lt;p&gt;This matches Monospace's pricing page, which lists row/column-level security, service accounts and custom roles and policies as Business-plan features. But it means the features Monospace leads with, per-agent identities and field-level rules, can't be tried on the free self-hosted version without contacting sales. The agent also noted that the licence limits weren't on any docs page it read. It only found them through the API, after being refused.&lt;/p&gt;

&lt;h3&gt;
  
  
  A live write: done
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffgjzo9ey4oejno4dzfqo.png" alt="Integration · step 5, live write" width="799" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What it took
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Human step&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"It's the one at monospace.io."&lt;/td&gt;
&lt;td&gt;The agent's search for Monospace found only fonts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin account, licence agreement, workspace (7m 27s)&lt;/td&gt;
&lt;td&gt;Expected, since a person has to accept the licence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create an API key in the UI&lt;/td&gt;
&lt;td&gt;Agents can't create their own key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restart Claude Code&lt;/td&gt;
&lt;td&gt;Needed to load a newly added MCP server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole session took about an hour and a half, including our steps. The agent never used Monospace's agent skills plugin, SDK or CLI. It skipped the skills plugin because "it wasn't needed for this".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration: OK (3 / 4)&lt;/strong&gt; — Working MCP integration with correct answers; field-level permissions blocked on the free plan&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Monospace can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Let people try governance without a sales call
&lt;/h4&gt;

&lt;p&gt;A trial licence that a developer can get without contacting sales, or a small allowance of custom policies and service accounts on the free plan, would let agents demonstrate per-agent identities and field-level rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  Document the free plan's limits where agents look
&lt;/h4&gt;

&lt;p&gt;Put the Starter limits (0 custom policies, roles and service accounts) in the docs next to the access control and API key pages, so an agent can tell a user before it builds a policy that can't be saved.&lt;/p&gt;

&lt;h4&gt;
  
  
  Add request examples for the API calls agents need
&lt;/h4&gt;

&lt;p&gt;Creating a data source and writing a policy were the two places the agent had to guess. One complete example request for each would remove most of the trial and error.&lt;/p&gt;

&lt;h4&gt;
  
  
  Make the API keys page point to personal keys
&lt;/h4&gt;

&lt;p&gt;On the free plan, the prominent API Keys page leads to a paywall, while the working option is under Account → Access. A link from one to the other would save the confusion.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for Monospace
&lt;/h2&gt;

&lt;p&gt;Monospace has built a lot for agents. It has an MCP server, &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;, Markdown docs, an OpenAPI spec on every instance, an agent skills plugin, and an onboarding screen that tells you how to connect Claude Code. Once an agent was connected, it loaded data, answered questions correctly and made live writes, all through MCP.&lt;/p&gt;

&lt;p&gt;The problems come before and around that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agents only found it by searching for its tagline, and in one test the agent recommended Directus for its job.&lt;/li&gt;
&lt;li&gt;Without the domain in the prompt, the agent either couldn't identify it or priced Directus instead.&lt;/li&gt;
&lt;li&gt;Field-level permissions and agent identities can't be tried on the free plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first two are fixable with content, such as pages built around the problems agents get asked about, clear "Monospace from Directus" naming, and the key facts on the homepage. The third is a pricing decision, but documenting the limits would at least stop agents and developers from running into them by surprise.&lt;/p&gt;

&lt;p&gt;Cloudflare's &lt;a href="https://isitagentready.com/" rel="noopener noreferrer"&gt;isitagentready.com&lt;/a&gt;, which checks how readable a website is for AI agents, scored Monospace's docs at &lt;a href="https://docs.monospace.io/en" rel="noopener noreferrer"&gt;docs.monospace.io&lt;/a&gt; 20 out of 100. Those are the docs the agent relied on in the integration, through their &lt;code&gt;llms.txt&lt;/code&gt; and Markdown pages.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>monospace</category>
      <category>directus</category>
    </item>
    <item>
      <title>SpacetimeDB AX Audit: A Review for AI Agents</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 12:11:48 +0000</pubDate>
      <link>https://dev.to/ritza/spacetimedb-ax-audit-a-review-for-ai-agents-eho</link>
      <guid>https://dev.to/ritza/spacetimedb-ax-audit-a-review-for-ai-agents-eho</guid>
      <description>&lt;p&gt;SpacetimeDB's homepage is written for AI agents. Its headline is "Let AI ship the whole backend, glue-free", and the site ships an &lt;code&gt;llms.txt&lt;/code&gt;, an agent setup guide, a Claude Code plugin, agent skills, an MCP server built into its CLI, and a public benchmark of how well LLMs write code for it.&lt;/p&gt;

&lt;p&gt;In our tests, a coding agent built a working, live-updating app on SpacetimeDB with no help from us. It used none of that tooling. And when we asked agents which backend to use for a web app, they picked Convex or Supabase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://spacetimedb.com" rel="noopener noreferrer"&gt;SpacetimeDB&lt;/a&gt; is a database that is also your server, made by Clockwork Labs. We put it through our &lt;a href="https://techstackups.com/articles/what-is-agent-experience-and-why-should-you-care/" rel="noopener noreferrer"&gt;agent experience (AX)&lt;/a&gt; audit, following our &lt;a href="https://techstackups.com/articles/how-to-do-an-ax-audit/" rel="noopener noreferrer"&gt;AX audit method&lt;/a&gt;. The audit has four stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness:&lt;/strong&gt; does the model know SpacetimeDB without searching?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability:&lt;/strong&gt; can an agent find SpacetimeDB when it's looking for a tool like it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation:&lt;/strong&gt; does the agent speak well of SpacetimeDB, and recommend it over other options?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration:&lt;/strong&gt; can the agent build a working proof of concept with SpacetimeDB?&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How we tested&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All testing used Claude Opus 5.5 (&lt;code&gt;claude-opus-5-5&lt;/code&gt;) in Claude Code 2.1.285, on 7 October 2026. Every session started in a fresh, empty folder with no project files, memory, MCP servers, skills or plugins. Awareness questions ran with no tools at all, and discoverability and reputation questions with web search and web fetch only. The integration ran in a fresh Claude Code profile with no settings, plugins or history, with every command approved by us. We ran each prompt once, so treat these results as one developer's experience rather than a benchmark.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Scores at a glance
&lt;/h2&gt;

&lt;p&gt;The model knows SpacetimeDB well, but as the game backend it was in 2025. It doesn't know the 2026 rebrand, the push into web apps or the agent tooling. Agents find it straight away for multiplayer games, but for web apps it comes second to Convex, and agents recommend Convex or Supabase over it. In the integration, the agent installed it, built a live page and hid private data without any help from us.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness: OK (3 / 4)&lt;/strong&gt; — Knows the games database well; doesn't know the rebrand, the web push or the agent tooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability: OK (3 / 4)&lt;/strong&gt; — Top pick for multiplayer games; second to Convex for web apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation: POOR (2 / 4)&lt;/strong&gt; — Recommended for games; loses to Convex and Supabase for web apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration: GOOD (4 / 4)&lt;/strong&gt; — Live app with private data, correct answers and no human steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overall: OK (3 / 4)&lt;/strong&gt; — Agents build well on it, but don't choose it for the web apps it now targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See our &lt;a href="https://techstackups.com/articles/how-to-do-an-ax-audit/" rel="noopener noreferrer"&gt;AX audit rubric&lt;/a&gt; for how we score each stage. The overall score is the average of the four.&lt;/p&gt;




&lt;h2&gt;
  
  
  Awareness: does the model know SpacetimeDB?
&lt;/h2&gt;

&lt;p&gt;Awareness tests what a model knows from training data alone, with no web search. SpacetimeDB has been public since 2023 and reached 1.0 in March 2025, so we expected the model to know it. The open question was whether it knew what SpacetimeDB has become in 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OK (3 / 4)&lt;/strong&gt; — Knows the games database well; doesn't know the rebrand or agent tooling&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Unknown, or confused with something else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Recognises the name, details vague or invented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Accurate description from memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Accurate, and recommended unprompted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We started with two questions that don't name SpacetimeDB. Each answer below comes from a fresh session with no tools.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5y03wjrmfhig57i9t01f.png" alt="Awareness · SpacetimeDB not named" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the opposite of SpacetimeDB's idea, which is to put game state and logic in the database. When we described that idea without the name, the model picked SpacetimeDB at once:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvm54a8i385uoxfptplxk.png" alt="Awareness · its pitch, not named" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Asked "What is SpacetimeDB (spacetimedb.com)?", it gave an accurate description of the product as it was around its 1.0 launch, including the Clockwork Labs and BitCraft background. It didn't mention 2.0, the web push, C++ support or any agent tooling, and it called the product "open source" under a licence that "converts to a permissive license". SpacetimeDB's FAQ calls it source-available, and the AGPL it converts to isn't a permissive licence.&lt;/p&gt;

&lt;p&gt;Then we asked about the new brand and the homepage tagline, and asked how to connect an agent to it over MCP:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd3a7hmuh31swkxxf13fv.png" alt="Awareness · new name, with its tagline" width="799" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyme8f9136g84468dbv41.png" alt="Awareness · MCP setup" width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The model didn't invent anything, and it said when it didn't know. Its picture of SpacetimeDB is the game backend from before 2.0.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness: OK (3 / 4)&lt;/strong&gt; — Knows the games database well; doesn't know the rebrand, the web push or the agent tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What SpacetimeDB can do about it
&lt;/h3&gt;

&lt;p&gt;Training data catches up slowly, so the fix is content that teaches the next training cut about 2.0. Posts and guides that pair the name with the new pitch ("SpacetimeDB, a reactive backend for web apps and AI agents") will help, as will a short page explaining that "Spacetime" and "SpacetimeDB" are the same product. Using one name everywhere would remove the doubt the model had about the rebrand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Discoverability: can an agent find SpacetimeDB?
&lt;/h2&gt;

&lt;p&gt;Discoverability tests whether an agent with web search finds the product when it's looking for something like it. We ran three six-question "funnels", one session each. Each funnel starts with a broad problem and narrows towards SpacetimeDB one question at a time, then names it at question 6. We recorded the first question where SpacetimeDB appeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OK (3 / 4)&lt;/strong&gt; — Top pick for games; second to Convex for web apps&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Only appears when named&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Only appears with its own positioning language&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Appears for a specific description of the problem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Top choice for broad problem descriptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each funnel asks from a different angle. Funnel A is a web developer, the audience SpacetimeDB 2.0 is aimed at. Funnel B is a game developer, the audience it started with. Funnel C builds apps by prompting a coding agent, which is who its homepage now speaks to.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Funnel&lt;/th&gt;
&lt;th&gt;Who's asking&lt;/th&gt;
&lt;th&gt;First question&lt;/th&gt;
&lt;th&gt;Where SpacetimeDB first appeared&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A web developer&lt;/td&gt;
&lt;td&gt;"I'm building a collaborative web app in React, a bit like a shared whiteboard with chat…"&lt;/td&gt;
&lt;td&gt;Question 3, as the runner-up to Convex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;A game developer&lt;/td&gt;
&lt;td&gt;"I'm making a multiplayer game in Unity. What should I use for the server side?"&lt;/td&gt;
&lt;td&gt;Question 2, as the top pick&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;Someone building with Claude Code&lt;/td&gt;
&lt;td&gt;"I build apps mostly by prompting Claude Code. What backend should I have it use…"&lt;/td&gt;
&lt;td&gt;Question 3, as a footnote&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For game developers, SpacetimeDB was the answer from the second question to the last:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F628n7bhg699pzwi41orc.png" alt="Discoverability · funnel B, question 2" width="799" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the web app in funnel A, SpacetimeDB appeared at question 3, when we asked for server logic inside the database, ACID transactions and query subscriptions. The agent's opening line was "Only two tools meet all four of your requirements: Convex and SpacetimeDB. For your app I'd choose Convex." Convex stayed the pick until the end. Then question 5 used SpacetimeDB's homepage tagline, and the search found Convex instead:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhnrm0ovc83l1loc4qw90.png" alt="Discoverability · funnel A, question 5" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Funnel C is the one SpacetimeDB's new homepage is written for. Here are its six questions, asked in one session, with the start of each answer.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feg65mzkfoy7crbwv7e4d.png" alt="Discovery funnel step 1" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2lpc141yelihp9oh3cyt.png" alt="Discovery funnel step 2" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdtt73es7d6dndu0kxay.png" alt="Discovery funnel step 3" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85io79d5e0fzuc7hsll9.png" alt="Discovery funnel step 4" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flhht0j0ks0t999jpaqii.png" alt="Discovery funnel step 5" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhzvhiqwjlteuyidk5rs0.png" alt="Discovery funnel step 6" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Question 3 described SpacetimeDB's agent tooling almost item by item. The agent ran a search for "SpacetimeDB MCP server llms.txt agent skills AI", found a community MCP server and the benchmark page, and concluded the official tooling wasn't there. It only found the plugin, the MCP server and &lt;code&gt;llms.txt&lt;/code&gt; after we named SpacetimeDB and it read the homepage. It also noticed a weakness of the benchmark: "The benchmark scores are at the ceiling. Opus 5.5 and two GPT-6 models all score 99.5%."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability: OK (3 / 4)&lt;/strong&gt; — Top pick for multiplayer games; second to Convex for web apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What SpacetimeDB can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Make the MCP server and agent skills findable by search
&lt;/h4&gt;

&lt;p&gt;The agent searched for SpacetimeDB's MCP server, &lt;code&gt;llms.txt&lt;/code&gt; and agent skills by name and found a community MCP server instead. A docs page titled in plain words, such as "Using SpacetimeDB with AI coding agents: MCP server, agent skills and llms.txt", would give that search an official result.&lt;/p&gt;

&lt;h4&gt;
  
  
  Put the taglines where search engines index them
&lt;/h4&gt;

&lt;p&gt;Searches for "reactive backend platform" returned pages about Convex, and searches for "glue-free" and "ship the whole backend" returned nothing. The agent found both phrases on spacetimedb.com only after we named the product. Using them in page titles, the docs and launch posts would let a tagline search land on SpacetimeDB.&lt;/p&gt;

&lt;h4&gt;
  
  
  Win the web app recommendation
&lt;/h4&gt;

&lt;p&gt;For web apps and agent-built apps, Convex was the agent's answer from the first or second question, before it had searched anything. The web articles it read were vendor round-ups of backends for AI coding agents, and SpacetimeDB wasn't in the ones it opened. Guides and comparisons written for web developers would give agents something to cite there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reputation: do agents recommend SpacetimeDB?
&lt;/h2&gt;

&lt;p&gt;Reputation tests what the agent thinks once SpacetimeDB is named, including whether it recommends it over competitors and whether it gets the facts right. We ran seven questions, each in a fresh session with web search. Convex and Supabase were the two products the agent named most often before it found SpacetimeDB, five answers each, so we used them for the head-to-heads. (We've audited Supabase's own agent experience before, in &lt;a href="https://techstackups.com/comparisons/supabase-vs-planetscale-agent-experience/" rel="noopener noreferrer"&gt;Supabase vs PlanetScale&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;POOR (2 / 4)&lt;/strong&gt; — Recommended for games; loses to Convex and Supabase for web apps&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; Not recommended, or core facts wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Heavy caveats, facts unreliable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Recommended for specific scenarios, facts mostly right&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; Recommended over alternatives, facts right&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Is SpacetimeDB good for web apps?
&lt;/h3&gt;

&lt;p&gt;Four of the five verdict questions were about web apps, the market SpacetimeDB 2.0 is aimed at, and all four went against it. The agent kept SpacetimeDB for very high-frequency shared state and recommended Convex or Supabase for everything else. The one game question got a clear yes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What do you think of SpacetimeDB for a real-time web app?&lt;/td&gt;
&lt;td&gt;"For a typical web app I'd lean against it."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpacetimeDB over Convex for a collaborative TypeScript and React app?&lt;/td&gt;
&lt;td&gt;No: "I'd pick Convex."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpacetimeDB over Supabase for the same app?&lt;/td&gt;
&lt;td&gt;No: "For most collaborative web apps I'd pick Supabase"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A 3-person team building a B2B SaaS dashboard&lt;/td&gt;
&lt;td&gt;"Short answer: no."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An indie studio making a multiplayer Unity game&lt;/td&gt;
&lt;td&gt;"Yes, shortlist it"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main risks of adopting SpacetimeDB (name only)&lt;/td&gt;
&lt;td&gt;All data held in memory, little public production evidence beyond BitCraft, the licence, and a single company behind it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing and licensing vs Convex and Supabase (name "Spacetime" only)&lt;/td&gt;
&lt;td&gt;Accurate, with the one-production-instance licence limit as the main warning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  SpacetimeDB vs Convex and Supabase
&lt;/h3&gt;

&lt;p&gt;Both head-to-heads used the same scenario, a real-time collaborative web app built in TypeScript and React. Here's the answer against Convex:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvqijw6wqyz0rtuyku025.png" alt="Reputation · SpacetimeDB vs Convex" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbtn5vxxcs5pqsnaz7kxc.png" alt="Reputation · a B2B SaaS dashboard" width="799" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa4dd2jtg0jw8hbzwqa8m.png" alt="Reputation · an indie Unity studio" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The facts were mostly right, including when we used only the name. The pricing and licensing answer read SpacetimeDB's pricing page and its licence file and got every plan, price and licence term right. The answer on risks identified the product from its bare name, and its storage limits match SpacetimeDB's limits page. Here's how its main claims checked out. The two errors came from pages the agent read instead of SpacetimeDB's own.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What the agent said&lt;/th&gt;
&lt;th&gt;What's true&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version 2.7 is the latest ("2.7 in June 2026")&lt;/td&gt;
&lt;td&gt;That came from an unofficial mirror of the GitHub repository. The latest release is 2.10.2, from 29 September 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpacetimeDB has "fewer built-in extras like file storage"&lt;/td&gt;
&lt;td&gt;The docs show files stored as binary columns in tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"The docs list joins and COUNT(*), but not GROUP BY, SUM/AVG, ORDER BY, window functions, subqueries or CTEs."&lt;/td&gt;
&lt;td&gt;True. The SQL reference lists &lt;code&gt;COUNT&lt;/code&gt; as the only aggregation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SpacetimeDB "admitted it had found and fixed problems" in its benchmarks&lt;/td&gt;
&lt;td&gt;True. Its May 2026 benchmarking post apologises for numbers that were "misleading"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five of the seven answers cited the same unofficial mirror of SpacetimeDB's repository, and two took the out-of-date version number from it. The answer that compared SpacetimeDB with Supabase never opened spacetimedb.com. In our own check afterwards, we found a Spacetime vs Supabase comparison page on SpacetimeDB's site that it didn't reach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reputation: POOR (2 / 4)&lt;/strong&gt; — Recommended for games; loses to Convex and Supabase for web apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What SpacetimeDB can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Answer the web app objections where agents look
&lt;/h4&gt;

&lt;p&gt;Every web app verdict came back to the same few concerns, which were reporting queries, webhooks, file storage, auth and a smaller ecosystem. Some were wrong, such as file storage, and some were right, such as the SQL limits. A docs page that addresses each one for web developers, including how to build reporting with only &lt;code&gt;COUNT&lt;/code&gt; in SQL, would give agents something to quote back.&lt;/p&gt;

&lt;h4&gt;
  
  
  Write comparisons that agents can use as a recommendation
&lt;/h4&gt;

&lt;p&gt;The agent quoted SpacetimeDB's own Convex post as evidence against it ("Even SpacetimeDB's team says so."). A comparison page that names the web apps SpacetimeDB is the better choice for would give agents a reason to recommend it for those apps.&lt;/p&gt;

&lt;h4&gt;
  
  
  Keep the official sources ahead of copies
&lt;/h4&gt;

&lt;p&gt;The mirror's out-of-date version number reached two answers. Release notes and the current version on pages that rank, such as the docs home and the GitHub README, would help the official sources win those searches.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration: can an agent build with SpacetimeDB?
&lt;/h2&gt;

&lt;p&gt;Integration tests whether an agent can install the product and build a "hello world" with as little human help as possible. We gave it four CSV files for a fictional business (50 customers, 30 products, 300 orders with 742 line items) and asked it to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up SpacetimeDB and get a local instance running.&lt;/li&gt;
&lt;li&gt;Load the CSV data into a database and make sure it can query it.&lt;/li&gt;
&lt;li&gt;Answer a question using the database: which region had the most revenue in the last three months, and who were its top three customers?&lt;/li&gt;
&lt;li&gt;Build a web page that shows revenue per region live, then refund one order and prove the page updated without a reload.&lt;/li&gt;
&lt;li&gt;Stop client apps reading customer email addresses, and prove it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We ran it in Claude Code's default permission mode, approving each command, so we could stop the agent from reading anything outside its folder. The integration ran in a brand-new Claude Code profile, so SpacetimeDB's plugin and skills would have loaded normally if the agent had installed them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GOOD (4 / 4)&lt;/strong&gt; — Live app with private data, correct answers and no human steps&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FAIL:&lt;/strong&gt; No working hello world&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POOR:&lt;/strong&gt; Works, but needed several human steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OK:&lt;/strong&gt; Works with short human steps and minor workarounds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GOOD:&lt;/strong&gt; End to end, headline feature included, at most one human step&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing: four and a half minutes, no help
&lt;/h3&gt;

&lt;p&gt;The agent knew which product we meant and how to install it. It downloaded the official install script, read the top of it, and ran it. It then started a local server, made the local server the CLI's default, and tested the setup with a throwaway test project, which it deleted afterwards.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz007q57w6hqwdh0jnhtc.png" alt="Integration · step 1, install" width="798" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Loading the data, guided by the CLI's rules file
&lt;/h3&gt;

&lt;p&gt;The agent wrote server code that defines four tables, storing money as whole cents, and loader functions that refuse to load the same table twice. It then wrote a Python script that sent the CSV rows to those functions. The row counts matched the files. It found that &lt;code&gt;order&lt;/code&gt; is a reserved word in SpacetimeDB's SQL and renamed the tables to plurals.&lt;/p&gt;

&lt;p&gt;Its main reference for SpacetimeDB's TypeScript API was the rules file that &lt;code&gt;spacetime init&lt;/code&gt; writes into every new project, as &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt; and Cursor rules. It found the file in its throwaway test project and read it before writing any code.&lt;/p&gt;

&lt;p&gt;The agent then probed SpacetimeDB's SQL and found it couldn't total, group or sort:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0us8s26f9ho3vvhx0sa7.png" alt="Integration · step 2, load and query" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That matches SpacetimeDB's SQL reference, where &lt;code&gt;COUNT&lt;/code&gt; is the only aggregation. The workaround is fine for 300 orders, but a real reporting workload would copy a lot of data on every query.&lt;/p&gt;

&lt;h3&gt;
  
  
  The hello world: a correct answer
&lt;/h3&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fca541kpqqww7mg3mol52.png" alt="Integration · step 3, hello world" width="798" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The answer matched the figures we calculated from the CSVs: $212,620.84 for North, excluding refunds. The only slip was in the wording, because "61 orders" includes the four refunded ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  The live page: updated 23 ms after the refund
&lt;/h3&gt;

&lt;p&gt;This step tests SpacetimeDB's headline feature, which is that clients subscribe to data and get changes pushed to them. The agent added a database function to change an order's status, deployed it without losing data, and generated typed client code with &lt;code&gt;spacetime generate&lt;/code&gt;. It then built a small web page with SpacetimeDB's TypeScript client library that subscribes to the orders and customers tables.&lt;/p&gt;

&lt;p&gt;To prove the page updated without a reload, it opened the page in headless Chrome and refunded order 1001 from the command line. Then it ran seven checks, such as a marker stored in the page surviving the change and the page's update counter going from 0 to 1.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftechstackups.com%2Fimg%2Farticles%2Fspacetimedb-ax-audit%2Fintegration-live-page-before.png" alt="The agent's web page before the refund: order revenue by region, with West at 60 orders and $225,607.88, and 0 live updates" width="640" height="480"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ftechstackups.com%2Fimg%2Farticles%2Fspacetimedb-ax-audit%2Fintegration-live-page-after.png" alt="The same page after the refund, without a reload: West highlighted at 59 orders and $219,182.69, and 1 live update" width="640" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnw8gn7n2sioitaqhcakn.png" alt="Integration · step 4, live page and refund" width="799" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both figures match our own: West went from $225,607.88 to $219,182.69.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hiding customer emails: a private table and a public view
&lt;/h3&gt;

&lt;p&gt;The agent made the customers table private and added a public view that exposes only each customer's ID, name and region. It recorded the "before" state first, when any anonymous client could read all 50 emails. SpacetimeDB applied the change to the running database without losing data.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F74tubx023rtt3ugrkds6.png" alt="Integration · step 5, hide emails" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We checked it ourselves as an anonymous client. Reading the customers table returned "no such table: &lt;code&gt;customers&lt;/code&gt;. If the table exists, it may be marked private.", and the view returned names and regions without emails.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it used, and what it didn't
&lt;/h3&gt;

&lt;p&gt;Finally, we asked which SpacetimeDB docs and agent tools it had used:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxozztuj8jqi2qy8sqt0g.png" alt="Integration · step 6, tooling" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Agent time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install and run locally&lt;/td&gt;
&lt;td&gt;4.5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load the data&lt;/td&gt;
&lt;td&gt;7 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hello world&lt;/td&gt;
&lt;td&gt;Under a minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live page and refund&lt;/td&gt;
&lt;td&gt;7 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hide emails&lt;/td&gt;
&lt;td&gt;4 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole session took 45 minutes, including the time we spent checking each step, and we typed nothing but the six prompts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration: GOOD (4 / 4)&lt;/strong&gt; — Live app with private data, correct answers and no human steps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What SpacetimeDB can do about it
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Point the rules file at the rest of the agent tooling
&lt;/h4&gt;

&lt;p&gt;The rules file that &lt;code&gt;spacetime init&lt;/code&gt; writes was the only piece of SpacetimeDB's agent tooling the agent used. A short section in that file on the plugin, &lt;code&gt;spacetime mcp&lt;/code&gt; and &lt;code&gt;agent-setup.md&lt;/code&gt; would introduce agents to the rest at the point they're already reading.&lt;/p&gt;

&lt;h4&gt;
  
  
  Document the reporting pattern
&lt;/h4&gt;

&lt;p&gt;The agent hit the SQL limits by trial and error and built its own workaround. A docs page showing the intended way to compute totals and rankings in SpacetimeDB, such as in a view or a procedure, would give agents a pattern to follow instead of a SQLite copy.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for SpacetimeDB
&lt;/h2&gt;

&lt;p&gt;SpacetimeDB is a good product for an agent to build on. Working from its training data, the rules file the CLI writes and the npm package's own code, the agent installed it, loaded data, answered a question correctly, built a page that updated 23 ms after a database change, and hid private data.&lt;/p&gt;

&lt;p&gt;The problems come before an agent starts building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From memory, agents know SpacetimeDB as a game backend, and its new tagline sounds like Convex to them.&lt;/li&gt;
&lt;li&gt;For web apps, agents recommend Convex or Supabase. SpacetimeDB lost both head-to-heads and the SaaS question.&lt;/li&gt;
&lt;li&gt;Agents searching for its MCP server, skills and &lt;code&gt;llms.txt&lt;/code&gt; found a community MCP server instead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Guides for web developers, comparison pages that recommend SpacetimeDB for specific kinds of app, and an agent tooling page that ranks for those searches would each address one of these.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>database</category>
      <category>reviews</category>
    </item>
    <item>
      <title>Orbital vs Hasura vs Apollo: AI Agent Review</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 11:22:22 +0000</pubDate>
      <link>https://dev.to/ritza/orbital-vs-hasura-vs-apollo-ai-agent-review-i3c</link>
      <guid>https://dev.to/ritza/orbital-vs-hasura-vs-apollo-ai-agent-review-i3c</guid>
      <description>&lt;p&gt;Orbital, Hasura, and Apollo all do similar things. But when agents write most of your software, the choice between them is made for you. Here’s which one your agent prefers, and why.&lt;/p&gt;

&lt;p&gt;Using Claude Code, we followed our &lt;a href="https://techstackups.com/articles/how-to-do-an-ax-audit/" rel="noopener noreferrer"&gt;audit method&lt;/a&gt; to assess their &lt;a href="https://techstackups.com/articles/what-is-agent-experience-and-why-should-you-care/" rel="noopener noreferrer"&gt;agent experience (AX)&lt;/a&gt; across four stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four stages of an AX audit
&lt;/h2&gt;

&lt;p&gt;The audit follows the funnel a developer goes through with their agent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Awareness → Discoverability → Reputation → Integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We started with what the model knew without tools, then tested online discovery and recommendations with web search and fetch, and finally attempted a working integration with the full toolset. &lt;a href="https://techstackups.com/articles/orbital-ax-audit/#the-four-stages-of-an-ax-audit" rel="noopener noreferrer"&gt;Explore the interactive audit diagram and exact prompts in the original article&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Awareness:&lt;/strong&gt; Does the model already know about the product, without tools?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discoverability:&lt;/strong&gt; Does it find the product when it searches online?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation:&lt;/strong&gt; Does it recommend the product over alternatives, and get the facts right?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration:&lt;/strong&gt; Can it build and verify a small working integration?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each stage gets a score from 1 to 4: &lt;strong&gt;GOOD (4)&lt;/strong&gt; means little friction; &lt;strong&gt;OK (3)&lt;/strong&gt; means minor gaps; &lt;strong&gt;POOR (2)&lt;/strong&gt; means a partial result with significant gaps; &lt;strong&gt;FAIL (1)&lt;/strong&gt; means the product is unknown or the task doesn't work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How we tested&lt;/strong&gt;&lt;br&gt;
All testing used Claude Opus 5.5 (&lt;code&gt;claude-opus-5-5&lt;/code&gt;) in Claude Code 2.1.287, on 13–14 October 2026. Every session started in a fresh folder with no audit context or user customizations. Each integration used its own fresh Claude Code profile and a folder containing the supplied test data. Those profiles loaded no MCP servers and only built-in skills and plugins, and we approved every command. Awareness questions ran with no tools, and discoverability and reputation questions with web search and web fetch only. We ran each prompt once, so treat these results as one developer's experience rather than a benchmark.&lt;/p&gt;

&lt;p&gt;We chose Hasura and Apollo GraphQL by asking six questions about Orbital's job and its alternatives, and taking the products the answers named most. Both appeared in all six answers. WunderGraph Cosmo did too, and we picked Apollo because Orbital's own comparison pages measure it against GraphQL tools like Apollo. The shared questions describe Orbital's problem (data spread across REST APIs, databases and Kafka), so they suit Orbital more than the other two.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What are Orbital, Hasura and Apollo GraphQL?
&lt;/h2&gt;

&lt;p&gt;All three let an app or an agent ask one API for data that lives in several systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://orbitalhq.com" rel="noopener noreferrer"&gt;Orbital&lt;/a&gt; sits in front of your existing APIs, databases and message streams. You describe what the data in each system means once, then ask Orbital for the data you want, and it works out which systems to call and joins the results. Our &lt;a href="https://techstackups.com/guides/orbital-quickstart/" rel="noopener noreferrer"&gt;Orbital quickstart&lt;/a&gt; covers how it works in more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hasura.io" rel="noopener noreferrer"&gt;Hasura&lt;/a&gt; turns databases and APIs into one GraphQL API, and its company now also sells an AI agent product, PromptQL. &lt;a href="https://www.apollographql.com" rel="noopener noreferrer"&gt;Apollo GraphQL&lt;/a&gt; makes widely used GraphQL tools, including a router that combines many services into one API and Connectors that map REST APIs into it without code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results at a glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥇 Hasura (1st)&lt;/strong&gt; — Known and found by agents; retrieved credentials and ran a script after browser login&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥈 Apollo GraphQL (2nd)&lt;/strong&gt; — Known and found by agents; ran a script after we logged in and created a key&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🥉 Orbital (3rd)&lt;/strong&gt; — Won the named head-to-heads; never found unprompted, and its script stopped at authentication&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Orbital&lt;/th&gt;
&lt;th&gt;Hasura&lt;/th&gt;
&lt;th&gt;Apollo GraphQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Awareness&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;td&gt;GOOD (4)&lt;/td&gt;
&lt;td&gt;GOOD (4)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discoverability&lt;/td&gt;
&lt;td&gt;FAIL (1)&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reputation&lt;/td&gt;
&lt;td&gt;GOOD (4)&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;FAIL (1)&lt;/td&gt;
&lt;td&gt;OK (3)&lt;/td&gt;
&lt;td&gt;POOR (2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Overall (average)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.25&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The averages are not rounded. Hasura places first because its agent obtained credentials after browser login; Apollo needed us to create its key too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Orbital vs Hasura vs Apollo GraphQL: which should you choose?
&lt;/h2&gt;

&lt;p&gt;The agent's recommendation depended on your data: "use Orbital to combine mixed sources including Kafka without code, Hasura for an instant API over mainly SQL databases, and Apollo to federate GraphQL services owned by many teams."&lt;/p&gt;

&lt;p&gt;Agents chose Orbital when the data includes Kafka topics and you want joins without writing code for each system, and it won every head-to-head on those grounds. Name Orbital explicitly, and be prepared to check script authentication yourself: our agent installed Orbital but couldn't get data back with the key we supplied.&lt;/p&gt;

&lt;p&gt;Agents recommended Hasura when "your data is mostly relational and what you want is a fast, auto-generated GraphQL API over it". Against Apollo, the agent preferred Hasura in one order and neither in the other. Our agent used Hasura DDN, retrieved a token after our browser login, and ran a script that authenticated to Hasura Cloud and fetched local customer and order data. The local data queries used no token. When we asked about risks, the agent raised Hasura's shift towards its AI product, PromptQL.&lt;/p&gt;

&lt;p&gt;Agents preferred Apollo when "Your consumers want GraphQL" or "Your sources are mostly REST", and said it "has a much larger community and is easier to hire for". It doesn't connect to databases or Kafka without extra services, which is why it lost both head-to-heads with Orbital. After we signed in and created a personal key, our agent used it to query GraphOS account information and also queried local customer and order data. The local router used no credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Awareness: does the model know Orbital?
&lt;/h2&gt;

&lt;p&gt;Awareness tests what a model knows from training data alone, with no web search.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orbital: OK (3 / 4)&lt;/strong&gt; — Knows it as a data gateway; doesn't know "governed context layer"; never named unprompted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hasura: GOOD (4 / 4)&lt;/strong&gt; — Accurate, and named for Orbital's job without a search&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apollo GraphQL: GOOD (4 / 4)&lt;/strong&gt; — Accurate, and named for Orbital's job without a search&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We first described Orbital's job without naming any product. Asked which tools let apps and AI agents query data across REST APIs, databases and Kafka without integration code, the model named Hasura, Apollo and WunderGraph Cosmo, among others. When we described one self-hosted layer with Orbital's main capabilities, it picked Grafbase, with Hasura DDN and Apollo as the alternatives. Orbital appeared in neither answer.&lt;/p&gt;

&lt;p&gt;Then we named each product with its domain, in a fresh session with no tools: "What is Orbital (orbitalhq.com)?", and the same question for Hasura (hasura.io) and Apollo GraphQL (apollographql.com). The model described all three accurately:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Answer (excerpt)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;Orbital&lt;/strong&gt; (orbitalhq.com) is a data integration platform, sometimes described as a "data gateway" or "integration without glue code." It comes from the team that previously built &lt;strong&gt;Vyne&lt;/strong&gt;, which Orbital is essentially the successor to."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hasura&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;Hasura&lt;/strong&gt; is a company and an open-source product that automatically generates a &lt;strong&gt;GraphQL API&lt;/strong&gt; (and, in newer versions, other API styles) on top of your existing databases and services."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apollo GraphQL&lt;/td&gt;
&lt;td&gt;"&lt;strong&gt;Apollo GraphQL&lt;/strong&gt; (apollographql.com) is a company that builds a widely used platform and set of open-source tools for working with &lt;strong&gt;GraphQL&lt;/strong&gt;, the query language for APIs that Facebook originally created."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Orbital, it described Taxi, the query language, joins across APIs, databases and Kafka, and the self-hosted Docker setup, but didn't mention AI agents or MCP. For Apollo, it mentioned the Apollo MCP Server.&lt;/p&gt;

&lt;p&gt;Next we used each company's tagline from its own site, with no domain. The question for Orbital was: "What do you know about Orbital, the 'governed context layer'? What does it do, and who is it for? Say clearly if you don't know." Hasura's and Apollo's versions swapped in their own name and tagline. Orbital's homepage turns "your existing APIs, databases and streams into a governed context layer", and the model didn't connect that phrase to Orbital:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Tagline&lt;/th&gt;
&lt;th&gt;Answer (excerpt)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;"governed context layer"&lt;/td&gt;
&lt;td&gt;"I don't know what this is. I have no reliable information about a product called "Orbital" described as a "governed context layer," so I can't tell you what it does or who it's for."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hasura&lt;/td&gt;
&lt;td&gt;"universal data access layer"&lt;/td&gt;
&lt;td&gt;"…It is metadata-driven, uses a pluggable connector spec (NDC) for arbitrary data sources, and is where the "universal data access layer" positioning mainly comes from." (about Hasura DDN)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apollo GraphQL&lt;/td&gt;
&lt;td&gt;"domain model for AI"&lt;/td&gt;
&lt;td&gt;"I'm not certain Apollo uses that tagline, or how they currently frame it. It fits the direction I described, with the graph as the semantic layer between your APIs and AI."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Orbital, it listed Orbital Insight, a geospatial analytics company, as one of several unrelated things called Orbital. For Hasura and Apollo, it described the products accurately either way.&lt;/p&gt;

&lt;p&gt;Asked how to join two REST APIs with Orbital on a laptop, the model gave the right shape of a setup: Docker, a Taxi project with a shared customer ID type, and a query. Its first command saved &lt;code&gt;https://start.orbitalhq.com&lt;/code&gt; as a Docker Compose file, even though that address serves Orbital's launcher script. It also didn't mention the sign-in Orbital needs on first start.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you're choosing
&lt;/h3&gt;

&lt;p&gt;Without web search, an agent suggests Hasura or Apollo for this job. If you want Orbital considered, name it. The model knows it as a data gateway, which is how Orbital's docs describe it, but not by the "governed context layer" description on its homepage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Discoverability: can an agent find Orbital?
&lt;/h2&gt;

&lt;p&gt;Discoverability tests whether an agent with web search finds a product while looking for a tool like it. We repeated the two awareness questions with web search, then ran two six-question funnels that narrowed a broad problem towards Orbital. The first four questions were the same for all three companies. Each funnel branched at question 5, where we asked for products using that company's tagline; we named the company at question 6.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orbital: FAIL (1 / 4)&lt;/strong&gt; — Only found when named, in both funnels; its tagline search found Cube&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hasura: OK (3 / 4)&lt;/strong&gt; — Named at question 1 or 3; its tagline search found it both times&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apollo GraphQL: OK (3 / 4)&lt;/strong&gt; — Named at question 1 or 3; its tagline search found it once&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With web search, the two open questions named Hasura and Apollo again, but not Orbital. One query came close to Orbital's description: &lt;code&gt;self-hosted GraphQL federation REST databases Kafka no resolvers field-level authorization 2026&lt;/code&gt;. The answer said no tool absorbs upstream API changes:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feffxf2ug59r1hrf3v1cm.png" alt="Request and response about a self-hosted data layer and upstream API changes" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Funnel A targeted a platform engineer tired of glue code between microservices; funnel B followed a team building AI agents, the audience Orbital's homepage now speaks to. Here is funnel B on the Orbital branch, with the start of each answer:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrgi7yqaf16n7xixbsmu.png" alt="Discovery funnel step 1" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa6l1fzg8sdkg6u53kb5.png" alt="Discovery funnel step 2" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsrnvblxhk9fr8mgz5bbb.png" alt="Discovery funnel step 3" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxyr3mb36j26v2dpyeg04.png" alt="Discovery funnel step 4" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjr1ngo3zuyxiw0edsf2r.png" alt="Discovery funnel step 5" width="795" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F55mhn0kirz0vsh11bzsy.png" alt="Discovery funnel step 6" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At question 4 the agent searched for &lt;code&gt;"context layer" OR "data gateway" for AI agents startup launch 2026 governed access internal systems&lt;/code&gt;, which uses both of Orbital's descriptions, without finding Orbital. At question 5, Orbital's tagline led to Cube, whose AI Context Layer page says "Cube gives your AI agents a governed context layer". Funnel A's tagline search went the same way, listing Cube, Starburst, Denodo and Atlan:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbasxmwjpvk4oqs05b7ge.png" alt="Tagline search request and response: governed context layer leads to Cube" width="798" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Funnel&lt;/th&gt;
&lt;th&gt;Orbital first appears&lt;/th&gt;
&lt;th&gt;Hasura first appears&lt;/th&gt;
&lt;th&gt;Apollo GraphQL first appears&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A: platform engineer&lt;/td&gt;
&lt;td&gt;Question 6 (named)&lt;/td&gt;
&lt;td&gt;Question 1, in a list of federation tools&lt;/td&gt;
&lt;td&gt;Question 1, in the same list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B: team building AI agents&lt;/td&gt;
&lt;td&gt;Question 6 (named)&lt;/td&gt;
&lt;td&gt;Question 3&lt;/td&gt;
&lt;td&gt;Question 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hasura's tagline, "universal data access layer", returned Hasura in both funnels. Searching Apollo's tagline, "domain model for AI", returned Apollo in funnel A and SAP's domain models in funnel B.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you're choosing
&lt;/h3&gt;

&lt;p&gt;If you shortlist tools by asking an agent to search, expect GraphQL federation tools: The Guild's Hive stack, Grafbase, WunderGraph Cosmo, Apollo and Hasura. Orbital didn't come up in any of our searches for this problem, so add it to the list yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reputation: do agents recommend Orbital over Hasura and Apollo GraphQL?
&lt;/h2&gt;

&lt;p&gt;Reputation tests what an agent with web search says about each product once it's in the conversation. Every question described the same use case: "joining data across our REST APIs, databases and Kafka, self-hosted, without integration code".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orbital: GOOD (4 / 4)&lt;/strong&gt; — Picked over both, in every head-to-head and in the three-way question&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hasura: OK (3 / 4)&lt;/strong&gt; — Recommended when the data is mostly in databases; split with Apollo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apollo GraphQL: OK (3 / 4)&lt;/strong&gt; — Recommended for GraphQL teams with mostly REST APIs; lost to Orbital, split with Hasura&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Asked for the single best tool, with no product named, the agent picked ClickHouse and didn't mention any of the three.&lt;/p&gt;

&lt;p&gt;Once all three were named, the agent chose Orbital, answering from what it already knew without a search:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffw7k5uwu27xbqmqj07yn.png" alt="Three-way recommendation request and response: the agent picks Orbital" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Apollo vs Hasura: how do they compare with Orbital?
&lt;/h3&gt;

&lt;p&gt;Orbital beat both Hasura and Apollo, in both orders. For Hasura vs Apollo GraphQL, the result was a split. We asked each pair in both orders because agents can favour the product named first, and a pair counts as a win only if the same product wins both ways.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pair&lt;/th&gt;
&lt;th&gt;Named first&lt;/th&gt;
&lt;th&gt;Named second&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orbital vs Hasura&lt;/td&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;Orbital wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orbital vs Apollo GraphQL&lt;/td&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;Orbital wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hasura vs Apollo GraphQL&lt;/td&gt;
&lt;td&gt;Neither: "neither is a great fit for all three requirements together"&lt;/td&gt;
&lt;td&gt;Hasura&lt;/td&gt;
&lt;td&gt;Split&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In each Orbital head-to-head, the agent searched for Orbital and cited its Kafka support and joins that need no code per system. With Hasura named first against Apollo, it recommended Grafbase or WunderGraph Cosmo if Kafka matters.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw0vurpcece4i8vi1p5ee.png" alt="Apollo versus Orbital request and response: Orbital fits the mixed-source requirements better" width="799" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last question used each product's bare name, with no domain. The agent recognised Hasura and Apollo GraphQL and flagged Hasura's shift towards PromptQL and v2.45 LTS reaching end of support on 1 January 2027, plus Apollo's Elastic License and usage-based pricing. Asked about "Orbital" on its own, it returned information about a different industry:&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxrajrvxdem8pqwl18v1e.png" alt="Risks request and response: the agent confuses Orbital with space-based data centres" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you're choosing
&lt;/h3&gt;

&lt;p&gt;Named side by side, Orbital won on Kafka support and joins without per-system code. Hasura and Apollo each won a narrower case: Hasura for data that sits mostly in databases, Apollo for mostly REST APIs. Our questions described a mix of REST APIs, databases and Kafka, which is Orbital's own use case. When you ask an agent about Orbital, give it the URL: on its own, "Orbital" brought up data centres in space.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration: can an agent build with Orbital?
&lt;/h2&gt;

&lt;p&gt;Integration follows three steps: sign up or install, get a credential for a script, then write and run a script that returns real data. Each company had one continuous session. The agents had shell access, web search and fetch, but no browser tool. We handled browser steps when asked. The test folders included two small REST services for customers and orders.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orbital: FAIL (1 / 4)&lt;/strong&gt; — Installed, but the supplied key returned errors and no data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hasura: OK (3 / 4)&lt;/strong&gt; — Human browser login; agent retrieved the token and ran a script&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Apollo GraphQL: POOR (2 / 4)&lt;/strong&gt; — Human account access and key creation; agent ran a script&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first request was: "Sign me up for Orbital, or install it if that's how it works, and get it ready to use. Do what you can yourself and tell me where you need me." Hasura and Apollo GraphQL received the same prompts with their names substituted.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company&lt;/th&gt;
&lt;th&gt;Sign up / install&lt;/th&gt;
&lt;th&gt;API key / access token&lt;/th&gt;
&lt;th&gt;Hello world&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Orbital&lt;/td&gt;
&lt;td&gt;Agent installed Docker stack; browser reused our existing account&lt;/td&gt;
&lt;td&gt;Human created a key and saved it to &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stuck: authenticated calls returned HTTP 500; no data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hasura&lt;/td&gt;
&lt;td&gt;Agent installed DDN CLI; human completed browser authentication&lt;/td&gt;
&lt;td&gt;Agent retrieved the CLI token and saved &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Agent ran a script: authenticated cloud project listing, plus local customer/order queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apollo GraphQL&lt;/td&gt;
&lt;td&gt;Agent installed Rover; human handled Studio account access&lt;/td&gt;
&lt;td&gt;Human created a personal key and saved &lt;code&gt;.env&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Agent ran a script: authenticated cloud account query, plus local customer/order queries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Orbital: installed, then stopped at authentication
&lt;/h3&gt;

&lt;p&gt;Orbital's agent downloaded and read the official launcher, started the Docker stack, and checked that its health endpoint returned 200. Our existing Orbital account signed in automatically in the browser.&lt;/p&gt;

&lt;p&gt;Asked to get a credential, the agent searched the docs, probed the API and inspected the local UI. It didn't obtain a key or write &lt;code&gt;.env&lt;/code&gt;. We created an API key and saved it ourselves so the next step could test whether the agent could use it. Requests with that key returned HTTP 500, and the agent found this message in Orbital's logs: &lt;code&gt;API Key authentication is not enabled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwaes31ju0fjtzsdz6bwt.png" alt="Orbital integration request and response: authentication fails and no data comes back" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We didn't test script authentication with other Orbital configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hasura: browser login, then the agent got the token
&lt;/h3&gt;

&lt;p&gt;Hasura's agent chose DDN, installed its CLI and asked us to complete browser authentication. After that it used &lt;code&gt;ddn auth print-access-token&lt;/code&gt; to save a token privately, without asking us to create or copy one.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F04uo51328i6zwo2ou93m.png" alt="Hasura credential request and response: the agent saves an access token privately" width="798" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent generated two OpenAPI connectors, fixed a Docker build failure and missing environment settings, then ran &lt;code&gt;query_hasura.py&lt;/code&gt;. Its saved token authenticated a Hasura Cloud project-list request. Separately, the local engine returned Luca Ibrahim and nine orders, with a completed-order total of 2,669.39, matching our test data. That local engine didn't require a key. The script fetched the customer and orders with two queries in one request; it hadn't created a relationship between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apollo: we created the key, then the script worked
&lt;/h3&gt;

&lt;p&gt;Apollo's agent installed Rover through Homebrew and tested a local customer query. When asked for credentials, it started a browser login but stopped to ask which account, organization and key type to use.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3o4fbi5sqb9o3xehpscb.png" alt="Apollo credential request and response: no key is created or saved" width="798" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We created a personal key in Studio and saved it to &lt;code&gt;.env&lt;/code&gt;. The agent wrote &lt;code&gt;query.py&lt;/code&gt;, fixed a missing client-version header, and ran it successfully. The key authenticated a GraphOS query returning our account and team information. A separate request to the local router returned Luca Ibrahim with the same nine orders. The local router answered without credentials.&lt;/p&gt;

&lt;p&gt;Hasura's and Apollo's integration scores reflect who handled account access and credentials, plus whether a script ran successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you're choosing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Orbital installed successfully, but browser sign-in and a supplied key didn't get our agent to a working script.&lt;/li&gt;
&lt;li&gt;Hasura required browser authentication, then the agent obtained its token and completed the script. It also repaired generated connector files along the way.&lt;/li&gt;
&lt;li&gt;Apollo's agent completed the script after we created and saved the personal key. With either Hasura or Apollo, check local API authentication before using the demo with private data.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>orbital</category>
      <category>agents</category>
    </item>
    <item>
      <title>Fish Audio vs ElevenLabs: Voice Cloning &amp; TTS</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:13:04 +0000</pubDate>
      <link>https://dev.to/ritza/fish-audio-vs-elevenlabs-voice-cloning-tts-dj1</link>
      <guid>https://dev.to/ritza/fish-audio-vs-elevenlabs-voice-cloning-tts-dj1</guid>
      <description>&lt;p&gt;If you're choosing between Fish Audio and ElevenLabs, the answer depends on whose voice you need. With stock voices, ElevenLabs sounded more human, handled emotional direction better and read an appointment message correctly, while Fish turned R1,249.50 into more than a million dollars. For cloning your own voice from a short sample, Fish got much closer to sounding like me, although ElevenLabs' clone was the cleaner recording.&lt;/p&gt;

&lt;p&gt;I tested Fish Audio Drama 3, released on 13 October 2026, against Eleven v4 using the same scripts and one take each, then cloned my own voice on both. Links to every clip are below, so you can hear the differences for yourself. Neither instant clone would convince someone who knows my voice, so if cloning is why you're here, read that section with that in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fish Audio vs ElevenLabs: the quick verdict
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;One-line reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instant voice cloning: likeness&lt;/td&gt;
&lt;td&gt;Fish Audio&lt;/td&gt;
&lt;td&gt;Much closer to my accent and tone, and kept my identity when the mood changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instant voice cloning: audio quality&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Crisper and more expressive, but sounded like a different person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural delivery (no tags)&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;More human, with excitement and pauses in the right places&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emotional direction (tags)&lt;/td&gt;
&lt;td&gt;ElevenLabs&lt;/td&gt;
&lt;td&gt;Carried emotion from one sentence to the next, and the laugh fitted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Names, numbers and currency&lt;/td&gt;
&lt;td&gt;ElevenLabs, by far&lt;/td&gt;
&lt;td&gt;Fish misread R1,249.50 as over a million dollars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How we tested&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Models.&lt;/strong&gt; Eleven v4 against Fish Audio Drama 3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stock voices.&lt;/strong&gt; ElevenLabs' Global Artist - Newsreader against Fish's Formal Announcer. Both are British female voices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same words on both.&lt;/strong&gt; The directed scripts used each tool's own tag wording (&lt;code&gt;[whispers]&lt;/code&gt; and &lt;code&gt;[laughs]&lt;/code&gt; on ElevenLabs, &lt;code&gt;[whispering]&lt;/code&gt; and &lt;code&gt;[laughing]&lt;/code&gt; on Fish), with &lt;code&gt;[excited]&lt;/code&gt; and &lt;code&gt;[pause]&lt;/code&gt; on both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single take.&lt;/strong&gt; Unlike &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;my Gemini vs ElevenLabs comparison&lt;/a&gt;, where I picked the better of two ElevenLabs outputs, every clip here is the first output from a single generation, with default settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloning&lt;/strong&gt; used the same 33.94-second recording of my voice on both. Fish's clone was made on the free website. ElevenLabs wouldn't let me save a clone on Free, so I made that one through an existing paid Creator API account, with background noise removal off.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fish Audio voice cloning vs ElevenLabs voice cloning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; can each tool sound like me from around 30 seconds of audio, and keep sounding like me when the line goes from everyday to relieved. I'm South African, so accent drift is easy for me to hear.&lt;/p&gt;

&lt;p&gt;Both upload screens accepted the same file. ElevenLabs asks for at least 10 seconds and recommends 3 minutes for the best quality. Fish also asks for 10 seconds, and marks around 30 seconds as enough for most voices. My recording is well short of the ElevenLabs recommendation, so treat this as a test of a quick clone from limited audio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix7auf7coinv6n60xbke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fix7auf7coinv6n60xbke.png" alt="ElevenLabs Instant Voice Cloning upload screen with reference-clean.m4a at 00:33 and a recommendation of 3 minutes of samples" width="799" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmrsp4q04ln16vc8kqqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmrsp4q04ln16vc8kqqq.png" alt="Fish Audio Instant Voice Clone upload screen showing a 34 second upload marked Enough for most voices" width="799" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both clones read the same new passage. It starts with everyday errands and ends on good news.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I left my keys next to the kettle again. If you're heading out, could you check whether the back door is locked? I should be home before the rain starts. Oh, and the good news: they finally approved the project! I can stop refreshing my inbox every five minutes. Let's get dinner somewhere nice tonight.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Listen: Instant voice cloning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same clean 33.94 s recording of my voice, same output text&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s3-clone-elevenlabs-take1.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;: Crisper audio, less like my voice&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s3-clone-fish-take1.mp3" rel="noopener noreferrer"&gt;Fish Audio (Drama 3) — MP3&lt;/a&gt;: Closer accent and identity, softer audio&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fish wins on likeness, with a clone much closer to my accent and tone. There's a hint of an American accent and the delivery is slightly sluggish, but it stayed recognisably me as the expression changed. It also sounds like someone reading text, which is fair, because the reference was me reading from my phone.&lt;/p&gt;

&lt;p&gt;ElevenLabs sounds crisper, and its expression is more distinct. But on "they finally approved the project!" it sounded like a different person. Fish delivered that line as relief rather than excitement, which I think suits someone who can finally stop refreshing their inbox, and it kept more of my identity while doing it.&lt;/p&gt;

&lt;p&gt;The Fish clip is a little damp next to ElevenLabs, and I'm happy to trade that for sounding like myself. To me the ElevenLabs clone sounded as if it had generalised my voice into something more generic. That's a listening impression, and I can't confirm anything about what's actually going on in the background.&lt;/p&gt;

&lt;p&gt;If I had to use one of these clones, I'd pick Fish. Neither is good enough that someone who knows my voice would definitely believe it was me, because the accent differences are still audible. I'd expect Fish's Professional Voice Cloning to do better, but I didn't test it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the paywalls are
&lt;/h3&gt;

&lt;p&gt;ElevenLabs let me upload the recording, listen to previews and see labels it had inferred from my voice. I liked that, and I thought the labels were accurate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4nmt2p4iedux61qrwotx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4nmt2p4iedux61qrwotx.png" alt="ElevenLabs Instant Voice Cloning preview screen with scenario previews, inferred labels for language, accent, gender and age, and a Save voice button" width="600" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I clicked &lt;strong&gt;Save voice&lt;/strong&gt; and hit a paywall. Instant cloning needs at least the Starter plan, and nothing earlier in the flow had made that clear to me. I'd rather find out before uploading my voice and going through the setup.&lt;/p&gt;

&lt;p&gt;Fish let me create the clone on Free, but only as a public voice. Making it private needs a paid plan. I deleted the public Fish clone and the temporary ElevenLabs clone after the test. Deletion removes the voice from your list, but Fish's terms include licences over publicly submitted content, so don't assume deleting a public clone undoes everything.&lt;/p&gt;

&lt;p&gt;Only clone a voice you own or have permission to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fish Audio TTS vs ElevenLabs: natural delivery and emotion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; whether each voice finds the emotion in a script on its own, and then how well it follows direction. The script is someone getting good news at work and then remembering it isn't public yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I wasn't going to tell anyone. We got the Lisbon project! I read the email three times before I believed it. Anyway, don't tell anyone yet. It's not official until Friday. Right. Now I just have to work out how to tell the team without grinning like an idiot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Natural read, no tags
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Listen: Natural read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Plain text, no tags. Does the voice infer the excitement and the pauses?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s1-natural-elevenlabs-take1.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;: More human, natural pause on 'Right.'&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s1-natural-fish-take1.mp3" rel="noopener noreferrer"&gt;Fish Audio (Drama 3) — MP3&lt;/a&gt;: Good emphasis on 'three times'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs sounded more human to me. At 0:02 it puts excitement into "we got the Lisbon project!", and the pause around "Right." sounds like someone collecting their thoughts.&lt;/p&gt;

&lt;p&gt;Fish inferred the emotion too. At 0:07 it slows down on "three times" to stress how often the email was read, which is a nice touch. Both handled "grinning like an idiot" well. The problem is the voice itself, which reminded me of older AI text to speech and made the emotion less convincing.&lt;/p&gt;

&lt;p&gt;That verdict is about these two stock voices, and a different Fish voice could change it. I'd use the ElevenLabs take as it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Directed read, with tags
&lt;/h3&gt;

&lt;p&gt;The directed version keeps the same words and adds tags. Here's the ElevenLabs script:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[whispers] I wasn't going to tell anyone. [excited] We got the Lisbon project! I read the email three times before I believed it. [whispers] Anyway, don't tell anyone yet. It's not official until Friday. [pause] Right. [laughs] Now I just have to work out how to tell the team without grinning like an idiot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fish got the same script with &lt;code&gt;[whispering]&lt;/code&gt; and &lt;code&gt;[laughing]&lt;/code&gt; in place of &lt;code&gt;[whispers]&lt;/code&gt; and &lt;code&gt;[laughs]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen: Directed read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whisper, excitement, a pause and a laugh, same position on both&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s1-directed-elevenlabs-take1.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;: Emotion carries between sentences&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s1-directed-fish-take1.mp3" rel="noopener noreferrer"&gt;Fish Audio (Drama 3) — MP3&lt;/a&gt;: Abrupt switch to a whisper at 0:10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Direction made both tools more expressive, and so more human, but ElevenLabs was still better. At 0:04 it speeds into the next sentence while holding on to the excitement from the one before, and it sounds like one person continuing a thought. Its laugh fits the conversation.&lt;/p&gt;

&lt;p&gt;Fish sounded as though each sentence had been given its own emotion and followed it literally. At 0:10 the switch to a whisper is abrupt and a bit uncanny, and the pace stays the same whatever the expression. Its laugh sounds like it's laughing because it was told to. I didn't hear either tool read a tag aloud or skip one, and I'd use the ElevenLabs directed take as it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Names, numbers and hard text
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What this tests:&lt;/strong&gt; the kind of appointment message a clinic might send. It has a name, a date, a time, a currency amount, a booking reference, a phone number and a network frequency, with no tags on either side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi, it's Dr. Nkosi's office. Your appointment is on 3 April 2026 at 14:45. The total is R1,249.50. Your booking reference is A-7419-X. If you need to change it, call 0800 555 0147. For the video appointment, please use the 2.4 GHz network if the other connection drops. Thanks, and we'll see you on Friday.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Listen: Names and numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Name, date, time, rand amount, booking reference, phone number, frequency&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s2-practical-elevenlabs-take1.mp3" rel="noopener noreferrer"&gt;ElevenLabs (eleven_v4) — MP3&lt;/a&gt;: Everything right, natural ending&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techstackups.com/img/comparisons/elevenlabs-vs-fish/s2-practical-fish-take1.mp3" rel="noopener noreferrer"&gt;Fish Audio (Drama 3) — MP3&lt;/a&gt;: Misread the rand amount&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ElevenLabs recognised R1,249.50 as rand and read the amount and the cents correctly. Fish made the most serious error in this test. What I heard was "R one million two hundred forty-nine dollars and fifty cents", which gets both the size of the amount and the currency wrong. I'd treat that as a serious error in anything that tells a customer what they owe.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;th&gt;Fish Audio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Dr. Nkosi&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correct to my ear, the "o" sounds like "aw"&lt;/td&gt;
&lt;td&gt;Recognisable, but the "o" sounds like "ow"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3 April 2026&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"April third 2026", natural&lt;/td&gt;
&lt;td&gt;"three April 2026", which sounds unnatural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;R1,249.50&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correct amount, rand and cents&lt;/td&gt;
&lt;td&gt;"R one million two hundred forty-nine dollars and fifty cents"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A-7419-X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both dashes spoken&lt;/td&gt;
&lt;td&gt;Left out the second dash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0800 555 0147&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Correct, zeros read the same way throughout&lt;/td&gt;
&lt;td&gt;Correct digits, but the first zero is "oh" and the rest are "zero"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both read 14:45 and 2.4 GHz correctly.&lt;/p&gt;

&lt;p&gt;Spoken separators in the booking reference help when someone has to read it back, because they mark where the parts break. The phone number is more a matter of taste, since Fish got the digits right but sounds odd mixing "oh" and "zero". My one wish for ElevenLabs is "oh-eight-hundred" for 0800, which is how Gemini read it in &lt;a href="https://techstackups.com/comparisons/gemini-vs-elevenlabs-voice-cloning-tts/" rel="noopener noreferrer"&gt;my Gemini vs ElevenLabs comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;ElevenLabs wins this by a long way, and it also ended the paragraph more naturally. Its take would need far less editing before I'd use it. I didn't try correcting Fish's reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Fish Audio free? Fish Audio vs ElevenLabs pricing
&lt;/h2&gt;

&lt;p&gt;Yes, Fish Audio's free plan gives you 8,000 credits a month, and it let me clone my voice as long as the clone was public. ElevenLabs is free for text to speech, but saving a voice clone needs a paid plan. Prices below were checked on 15 October 2026, in US dollars.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;ElevenLabs&lt;/th&gt;
&lt;th&gt;Fish Audio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free plan&lt;/td&gt;
&lt;td&gt;10,000 credits a month, no instant cloning, no commercial licence&lt;/td&gt;
&lt;td&gt;8,000 credits a month, 500 characters per generation, 3 public voice slots, personal non-commercial use only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry paid plan&lt;/td&gt;
&lt;td&gt;Starter: $6 a month for 30,000 credits, includes instant cloning&lt;/td&gt;
&lt;td&gt;Plus: $15 a month, or $132 a year billed annually, for 250,000 credits and 10 private voice slots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API price for the model I tested&lt;/td&gt;
&lt;td&gt;Eleven v4: $0.08 per 1,000 characters&lt;/td&gt;
&lt;td&gt;Drama 3: $50 per million characters ($0.05 per 1,000), 80% off until 12 November 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ElevenLabs' professional cloning starts on Creator at $22 a month. Its annual billing charges ten months' price for a year. Credits on the websites and dollars on the APIs are separate, so don't convert one into the other.&lt;/p&gt;

&lt;p&gt;My ElevenLabs website balance went from 6,064 to 5,191 credits for the two stock-voice tests. Fish went from 8,000 to 6,818, but that also covers the clone. The workloads are different, so these numbers don't tell you which is cheaper.&lt;/p&gt;

&lt;p&gt;Fish's first-time tips were helpful when I added my first directed script. The biggest friction on ElevenLabs was finding out about the cloning paywall at the last step.&lt;/p&gt;

&lt;p&gt;Both free plans restrict commercial use, so if you're making something for paid work, check the plan and voice permissions first (&lt;a href="https://help.elevenlabs.io/hc/en-us/articles/13313564601361-Can-I-publish-the-content-I-generate-on-the-platform" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt;, &lt;a href="https://fish.audio/plan/" rel="noopener noreferrer"&gt;Fish Audio&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Fish Audio a good ElevenLabs alternative?
&lt;/h2&gt;

&lt;p&gt;For cloning your own voice from a short sample, yes. For ordinary text to speech, ElevenLabs was better in every stock-voice test I ran. This covers two models and four short tests, so for a wider view, see &lt;a href="https://techstackups.com/comparisons/best-tts-2026-blind-benchmark/" rel="noopener noreferrer"&gt;Best TTS in 2026: blind benchmark&lt;/a&gt; and &lt;a href="https://techstackups.com/comparisons/cartesia-vs-elevenlabs/" rel="noopener noreferrer"&gt;Cartesia vs ElevenLabs&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ordinary voiceovers in English:&lt;/strong&gt; ElevenLabs. Its stock voice sounded more human with and without direction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything with prices, references or phone numbers:&lt;/strong&gt; ElevenLabs. After Fish's currency error, I wouldn't use it for that kind of text without checking every take.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloning your own voice from a short sample:&lt;/strong&gt; Fish. It sounded more like me.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>tts</category>
      <category>elevenlabs</category>
      <category>comparison</category>
    </item>
    <item>
      <title>MSI Titan 18 HX Dragon Edition Norse Myth AI Review: Can It Run Advanced AI Workloads Locally?</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Wed, 16 Sep 2026 09:37:48 +0000</pubDate>
      <link>https://dev.to/ritza/msi-titan-18-hx-dragon-edition-norse-myth-ai-review-can-it-run-advanced-ai-workloads-locally-af</link>
      <guid>https://dev.to/ritza/msi-titan-18-hx-dragon-edition-norse-myth-ai-review-can-it-run-advanced-ai-workloads-locally-af</guid>
      <description>&lt;p&gt;Watch the MSI Titan 18 HX run Qwen3.8-Flash-Next locally:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/4pk0wAULBCE" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;At the forefront of the AI race, Anthropic, OpenAI, and the other big players have been all over mainstream news for a couple of years now. As of August 2026, many people are comparing GPT Sol to Fable and sharing the latest attempts to one-shot a GTA clone. However, there is another race going on that gets much less attention, led by some lesser-known AI companies. Open-weight mixture-of-experts models such as Qwen have improved dramatically in capability, efficiency, and general usefulness. As they become more efficient, running them on consumer-grade hardware becomes more feasible.&lt;/p&gt;

&lt;p&gt;MSI recently sent us one of its Titan laptops to experiment with, and we had one main question: Can you really run a useful LLM on a laptop?&lt;/p&gt;

&lt;p&gt;The quick answer is yes, and the experience was much better than we expected. As a slightly cheesy example, here is a playable Three.js game that the local model one-shotted as a single HTML file: &lt;strong&gt;Press start&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://techstackups.com/demos/msi-titan-ai-review/qwen-lane-runner.html" rel="noopener noreferrer"&gt;Play the Qwen-built lane runner on TechStackUps →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Titan Is More Workstation Than Gaming Laptop
&lt;/h2&gt;

&lt;p&gt;Okay, so we mentioned that it is becoming feasible to run local models on consumer hardware. It is getting there, but this particular laptop from MSI is not your standard student laptop. It does not even really qualify as a high-end gaming laptop. It is closer to a chunk of the supercomputer hardware you might rent from &lt;a href="https://vast.ai/" rel="noopener noreferrer"&gt;Vast.ai&lt;/a&gt; or &lt;a href="https://lambda.ai/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt;, except that it comes with a keyboard and an enormous screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  96 GB of RAM Makes Local Qwen Possible
&lt;/h3&gt;

&lt;p&gt;The laptop has an Intel Core Ultra 9 285HX and an RTX 5090 Laptop GPU. Most importantly for this use case, it also has 96 GB of DDR5 RAM. The main specifications are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Specification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;Intel Core Ultra 9 285HX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;NVIDIA GeForce RTX 5090 Laptop GPU with 24 GB GDDR7 VRAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;96 GB DDR5-6400 RAM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display&lt;/td&gt;
&lt;td&gt;18-inch 3840 × 2400 Mini LED, 120 Hz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;2 TB PCIe Gen5 SSD plus 4 TB PCIe Gen4 SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 24 GB of VRAM is great, but the unusually large pool of system RAM is what makes this machine interesting for models that cannot fit entirely on the GPU. You can find the complete configuration on the &lt;a href="https://us.msi.com/Laptop/Titan-18-HX-Dragon-Edition-Norse-Myth-A2XWX/Specification" rel="noopener noreferrer"&gt;MSI specification page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dragon Edition Fully Commits to the Theme
&lt;/h3&gt;

&lt;p&gt;It also looks really cool. With an embossed aluminum top panel, a 4K display, and fancy gamer lights, the Titan looks and feels like a piece of premium technology. Lots of dragons. Battery for scale.&lt;/p&gt;

&lt;p&gt;Our package also came with a matching gaming mouse and mouse pad, plus an enormous 400 W charging brick.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxoz88gn4t7j4q3kpnr4x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxoz88gn4t7j4q3kpnr4x.jpg" alt="Embossed Norse dragon artwork on the closed MSI Titan laptop lid" width="800" height="1067"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkz8nvi9103zjpj0yxj95.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkz8nvi9103zjpj0yxj95.jpg" alt="Open MSI Titan Dragon Edition laptop with its matching accessories" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A 180-Billion-Parameter Model Fits, but Only at 2 Bits
&lt;/h2&gt;

&lt;p&gt;We originally planned to test several local models on this hardware, but then, with excellent timing, Alibaba's Qwen team released &lt;a href="https://huggingface.co/Qwen/Qwen3.8-Flash-Next" rel="noopener noreferrer"&gt;&lt;code&gt;Qwen3.8-Flash-Next&lt;/code&gt;&lt;/a&gt;. It is an open-weight mixture-of-experts model with 180 billion total parameters, but it activates only a small fraction of them for each token. That makes it far more practical to run locally than the headline parameter count suggests.&lt;/p&gt;

&lt;p&gt;The full-precision model is still far too large for a laptop, so we used Unsloth's 2-bit &lt;code&gt;UD-Q2_K_XL&lt;/code&gt; GGUF quantization. The download is about 73.5 GB. At runtime, it used roughly 21 GB of the RTX 5090's VRAM and about 75 GB of system RAM, leaving enough headroom for Windows, our agent harness, the monitor, and a browser. The model advertises support for up to 262,144 tokens of context. We tested it with a 32,768-token server allocation, but we did not establish that as the laptop's maximum.&lt;/p&gt;

&lt;p&gt;We therefore kept the tests focused on this Qwen model and compared it primarily with OpenAI's GPT-5.6 Luna, a cheap and capable cloud model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qwen Was Easy to Install but Needed Manual Tuning
&lt;/h2&gt;

&lt;p&gt;Installing the model and getting it running locally mostly required us to follow &lt;a href="https://unsloth.ai/docs/models/qwen3.8-next" rel="noopener noreferrer"&gt;Unsloth's Qwen3.8-Next guide&lt;/a&gt;. The out-of-the-box settings did not provide the best starting point, though: automatic memory fitting initially reduced the context to 8,192 tokens, which hurt performance considerably until we manually raised it.&lt;/p&gt;

&lt;p&gt;Loading the 73.5 GB model from cold took 60.1 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pi Kept the Local and Cloud Tests Comparable
&lt;/h3&gt;

&lt;p&gt;To compare the models easily, and because we generally like using it, we installed &lt;a href="https://pi.dev/docs/latest" rel="noopener noreferrer"&gt;Pi&lt;/a&gt; as our agent harness.&lt;/p&gt;

&lt;p&gt;This added another layer of configuration, which complicated later troubleshooting. Pi also loads its standard tool definitions into the context before a task begins, which is worth remembering when comparing context use with a direct API call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fej0k8kr2d7h6gwhappxs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fej0k8kr2d7h6gwhappxs.png" alt="Pi configured to use the local Qwen model" width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MSI Center and Our Dashboard Tracked Hardware and Tokens
&lt;/h3&gt;

&lt;p&gt;The laptop came with MSI Center pre-installed. For every test, we kept the laptop connected to its 400 W charger and selected the Extreme Performance profile under User Scenario. We used the Hardware Monitoring view in MSI Center to watch CPU and GPU load and temperatures, memory use, and other system values while Qwen ran.&lt;/p&gt;

&lt;p&gt;For the model-side measurements, we got Codex to make a small companion app that tails the latest JSONL session from Pi or Codex. It reports the outcome, elapsed time, generated tokens, thinking share, context use, and cache use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9hsbwoidgjooqrdqbbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu9hsbwoidgjooqrdqbbu.png" alt="Companion dashboard showing local model run metrics" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Qwen Overthought the Easy Test, Stayed Cool, and Built the Better Game
&lt;/h2&gt;

&lt;p&gt;We were not trying to produce a leaderboard from a handful of prompts. Instead, we picked tasks that exposed different parts of the experience: how the model uses its thinking budget, what a long agentic task does to the laptop, and whether the final output actually works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fibonacci: Qwen Did Nine Times More Work Than Necessary
&lt;/h3&gt;

&lt;p&gt;We started with the smallest possible coding task: "Write me Fibonacci in Python and save it to a file."&lt;/p&gt;

&lt;p&gt;The clip below shows the local run in progress. MSI Center tracks CPU and GPU load in the top left, our companion dashboard tracks time, tokens, and context below it, and Pi shows Qwen's work on the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gz4u5szy09ljfwnrwvp.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gz4u5szy09ljfwnrwvp.gif" alt="Qwen running the Fibonacci task locally while MSI Center and the monitoring dashboard track performance" width="640" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With its thinking budget turned up, Qwen produced four implementations, including an &lt;code&gt;O(log n)&lt;/code&gt; fast-doubling version. It also added command-line arguments, type hints, validation, and tests, and used four tool calls. The result took 71 seconds and generated 1,647 output tokens.&lt;/p&gt;

&lt;p&gt;Luna took eight seconds, generated 177 output tokens, and wrote a basic 12-line function. Qwen's work was better, but for this prompt it was comically over-engineered. Separate direct benchmarks put Qwen's dependable generation rate between 26 and 33 tokens per second. Raw speed was not the problem here; it decided to do about nine times as much work.&lt;/p&gt;

&lt;p&gt;That gave us a useful early lesson: a large thinking budget is not free intelligence. We switched thinking off for the remaining tests, and Qwen became much more direct without a noticeable drop in the quality of the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pelican: 89 GB of RAM and No Thermal Throttling
&lt;/h3&gt;

&lt;p&gt;The next test came from &lt;a href="https://simonwillison.net/2026/Aug/16/qwen-38-27b/" rel="noopener noreferrer"&gt;Simon Willison's Qwen3.8-Flash-Next write-up&lt;/a&gt;: create an SVG of a pelican riding a bicycle. Our early results were highly variable. Sometimes the model put real effort into the illustration; sometimes it did the least it could possibly get away with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzdiz5azl48usqt3e4z0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzdiz5azl48usqt3e4z0.png" alt="A tile of four varied early SVG attempts showing a pelican riding a bicycle" width="800" height="705"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make the comparison less dependent on how ambitious either model happened to feel, we added the word "detailed" to the prompt and ran it again with Qwen and Luna. Qwen is on the left below, and Luna is on the right. Both produced a recognizable, fairly detailed pelican on a bicycle, and Qwen's version looks substantially better.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflpw5pys4hb920t3x967.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflpw5pys4hb920t3x967.png" alt="Detailed pelican riding a bicycle SVGs from Qwen on the left and Luna on the right" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During the local run, system RAM peaked at about 89 GB, and VRAM stayed near 21.8 GB. The GPU peaked at 69°C and roughly 111 W. In a separate eight-minute sustained run, it again topped out at 69°C, averaged 63.7°C, and reached a brief 213.5 W power peak without thermal throttling or an upward temperature trend.&lt;/p&gt;

&lt;p&gt;The fans are not subtle. In one 90-second sample near the laptop, a phone sound meter averaged 62.1 dB-A and showed 65.7 dB-A when we took the screenshot. This is not a calibrated lab measurement, but it accurately captures the experience: under sustained load, the Titan sounds like it is moving a lot of air.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6ep6gupriigjm3g5o7j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe6ep6gupriigjm3g5o7j.jpg" alt="Phone sound meter showing 65.7 dB-A near the MSI Titan during a sustained workload" width="597" height="1137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While watching MSI Center, we saw the CPU temperature peak at about 89°C. When the job finished, it quickly dropped to its idle range of about 45°C to 50°C.&lt;/p&gt;

&lt;p&gt;In other words, the model pushed the memory and cooling system hard, but we did not hit a practical hardware limit. The laptop shed heat quickly, remained responsive throughout the run, and never throttled or crashed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three.js: Qwen Was Slower but Built the Only Playable Game
&lt;/h3&gt;

&lt;p&gt;For the final test, we asked both agents to make a three-lane endless runner in Three.js as a single HTML file. The prompt specified 21 requirements, including jumping, lane changes, obstacles, collisions, scoring, and restarting.&lt;/p&gt;

&lt;p&gt;Qwen took five minutes and 36 seconds, used 18 tool calls, and generated 9,266 output tokens. Luna took one minute and 37 seconds, used six tool calls, and generated 4,314 output tokens. However, Luna's attempt was vastly inferior.&lt;/p&gt;

&lt;p&gt;Although the score increased and the obstacles moved, the player was invisible because the camera framing placed it below the viewport. It also looked much more amateurish than Qwen's attempt and showed little ambition in its visuals. This is exactly why we care more about the output than a benchmark score.&lt;/p&gt;

&lt;p&gt;The short clips below show the two results side by side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qwen: slower, but playable&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonhyxi3pmp578wjdes62.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fonhyxi3pmp578wjdes62.gif" alt="Qwen's playable Three.js lane runner" width="640" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Luna: faster, but where is the player?&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfw1e9775hn6yc5i9nv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqrfw1e9775hn6yc5i9nv.gif" alt="Luna's Three.js lane runner with the player outside the visible frame" width="640" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The local model took more time and used more tokens, but it also produced the better result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local AI Is a Great Bonus, Not a Business Case
&lt;/h2&gt;

&lt;p&gt;The exact configuration we tested, with 96 GB of RAM, an RTX 5090, and 6 TB of storage, has been listed for about &lt;a href="https://www.newegg.com/msi-titan-18-18-0-geforce-rtx-5090-laptop-gpu-intel-core-ultra-9-285hx-uhd-96gb-memory-2tb-nvme-ssd-gen5x4-4tb-nvme-ssd-gen4x4-ssd/p/N82E16834156784" rel="noopener noreferrer"&gt;$6,200&lt;/a&gt;. Prices and availability vary, but that gives us a reasonable figure for a rough comparison. If the only goal is to avoid an AI bill, the math is brutal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;What $6,200 buys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://help.openai.com/en/articles/6950777-what-is-chatgpt-plus" rel="noopener noreferrer"&gt;ChatGPT Plus at $20 per month&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;About 26 years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://help.openai.com/en/articles/9793128-what-is-chatgpt-pro" rel="noopener noreferrer"&gt;ChatGPT Pro at $200 per month&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;About 31 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://developers.openai.com/api/docs/models/gpt-5.6-luna" rel="noopener noreferrer"&gt;GPT-5.6 Luna output tokens at $1.20 per million&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;About 5.2 billion tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That token figure is deliberately generous to the laptop. Output is Luna's most expensive token category, so a realistic mix of cheaper input, cached-input, and output tokens would require even more total tokens to reach $6,200. This also ignores the laptop's electricity use and depreciation, and assumes that a local model can replace every feature of the cloud service.&lt;/p&gt;

&lt;p&gt;As a purchase dedicated to running local LLMs, the Titan does not make financial sense. But that is not really the strongest case for it. If you already want a very powerful laptop for gaming, rendering, video editing, machine learning, or other heavy workflows, adding a capable, private local LLM to your toolbox is definitely worthwhile. Treat local inference as a valuable bonus, not the reason the machine pays for itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can the MSI Titan 18 HX Run Large AI Models Locally?
&lt;/h3&gt;

&lt;p&gt;Yes. We ran a 180-billion-parameter model using a 2-bit quantization, with the weights split between the RTX 5090's VRAM and system RAM. The machine handled it reliably, although the 89 GB RAM peak leaves little room for a larger model, higher-precision weights, or a much larger context allocation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Local AI Workloads Suit the Titan Best?
&lt;/h3&gt;

&lt;p&gt;It makes the most sense for single-user coding agents, rapid prototyping, and generative tasks where local processing, privacy, or avoiding per-token charges matters. It is less compelling as a high-throughput server or a machine shared by several users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can It Replace a Desktop AI Workstation or Cloud Platform?
&lt;/h3&gt;

&lt;p&gt;For some single-user workflows, yes, but not completely. It provides workstation-class local inference in a portable package, while a desktop remains easier to upgrade and cloud platforms offer a wider choice of frontier models and scalable compute.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>nvidia</category>
    </item>
    <item>
      <title>Best SERP API Comparison 2025: SerpAPI vs Exa vs Tavily vs ScrapingDog vs ScrapingBee</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Wed, 03 Dec 2025 13:49:46 +0000</pubDate>
      <link>https://dev.to/ritza/best-serp-api-comparison-2025-serpapi-vs-exa-vs-tavily-vs-scrapingdog-vs-scrapingbee-2jci</link>
      <guid>https://dev.to/ritza/best-serp-api-comparison-2025-serpapi-vs-exa-vs-tavily-vs-scrapingdog-vs-scrapingbee-2jci</guid>
      <description>&lt;p&gt;We tested five SERP APIs with standardized benchmarks to find which performs best for different use cases. &lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; delivered the fastest responses at 1.180 seconds with semantic search over a proprietary index at $5 per 1,000 queries. &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; aggregates and parses content for LLM applications. &lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; offers the lowest per-request pricing at under $0.001 at scale. &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; provides the broadest coverage with 20+ search engines. &lt;a href="https://www.scrapingbee.com" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; bundles SERP access with general web scraping.&lt;/p&gt;

&lt;p&gt;This comparison helps you choose a SERP API for SEO monitoring, AI agents, RAG applications, or price comparison tools. Each section includes benchmark data, pricing breakdowns, and code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview Comparison
&lt;/h2&gt;

&lt;p&gt;We tested five SERP APIs using this Python benchmark script that measured response time, success rate, cost, and free tier across 50 real-world queries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;Avg Response Time&lt;/th&gt;
&lt;th&gt;Success Rate&lt;/th&gt;
&lt;th&gt;Cost per 1,000 Searches&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.180s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$5&lt;/td&gt;
&lt;td&gt;2,000 one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scrapingdog&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.700s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$0.20-$0.06&lt;/td&gt;
&lt;td&gt;1,000+ one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tavily&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.885s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$5-$8&lt;/td&gt;
&lt;td&gt;1,000/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SerpAPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2.972s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$15&lt;/td&gt;
&lt;td&gt;250/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scrapingbee&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.257s&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;$0.56-$1.44&lt;/td&gt;
&lt;td&gt;66 one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3fkbi82wz3h900rf7j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3fkbi82wz3h900rf7j4.png" alt="SERP API Comparison Spider Chart" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Response times vary based on what each API delivers: &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; returns raw SERP data, &lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; performs semantic search with embeddings generation, and &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; aggregates content from multiple sources with AI ranking. These tests used free tiers, but some providers offer faster speeds for additional cost (for example, &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's Ludicrous Speed tiers&lt;/a&gt; claim 2.2x faster responses).&lt;/p&gt;

&lt;h1&gt;
  
  
  SerpAPI
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; scrapes 20+ search engines and returns structured JSON. The service handles CAPTCHA solving and proxy rotation automatically, so you make a request and get back parsed data without dealing with blocks or rate limits.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftojd6ad9na8mghn75grp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftojd6ad9na8mghn75grp.png" alt="SerpAPI Spider Chart" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 2.972s average response time with 100% success rate. Response times ranged from 0.070s to 13.308s, showing high variability. The free tier uses "Best Effort" speed with automatic refunds for failed searches. &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;Paid tiers offer Ludicrous Speed&lt;/a&gt; claiming 2.2x faster responses for time-sensitive applications. &lt;a href="https://serpapi.com/status" rel="noopener noreferrer"&gt;SerpAPI's status page&lt;/a&gt; reports 99.911% uptime over the last 30 days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (6.0/10):&lt;/strong&gt; Mid-tier performance at 2.972s average places SerpAPI fourth out of five tested APIs, though the wide variance suggests some queries complete in milliseconds while others take 10+ seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's pricing&lt;/a&gt; uses monthly subscriptions based on search volume. Plans start at $75/month for 5,000 searches, which works out to $15 per 1,000 searches or $0.015 per search. Production tier and above include &lt;a href="https://serpapi.com/legal-shield" rel="noopener noreferrer"&gt;U.S. Legal Shield&lt;/a&gt;, providing legal defense for crawling public search data under First Amendment rights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (3.0/10):&lt;/strong&gt; At $15 per 1,000 searches, SerpAPI costs 3-15x more than alternatives like Scrapingdog ($0.20) or Exa ($5), earning the lowest pricing score among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://serpapi.com/integrations" rel="noopener noreferrer"&gt;SerpAPI provides SDKs&lt;/a&gt; for Ruby, Python, JavaScript, Golang, PHP, Java, Rust, and .NET. Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;serpapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GoogleSearch&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Austin, Texas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://serpapi.com/playground" rel="noopener noreferrer"&gt;SerpAPI's Playground&lt;/a&gt; lets you test queries through a web interface before writing code. You modify parameters through form controls and see both the rendered SERP and JSON response. The dashboard tracks your usage with response time graphs and search history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kyiq6nsz1g138oni1t1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kyiq6nsz1g138oni1t1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (10.0/10):&lt;/strong&gt; Eight official SDKs, an interactive playground, comprehensive documentation, and real-time usage dashboards make SerpAPI the strongest developer experience among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;SerpAPI covers Google (including AI Mode and AI Overview), Bing, Yahoo, Baidu, DuckDuckGo, Yandex, Amazon, eBay, Walmart, Home Depot, YouTube, Google Scholar, Google Patents, TripAdvisor, and Yelp. Each platform has its own API endpoint with consistent JSON formatting. Most competitors support 3-5 search engines at most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (10.0/10):&lt;/strong&gt; Coverage of 20+ search engines and specialized platforms far exceeds any competitor, making SerpAPI the only choice for applications needing multi-engine support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 250 searches per month with no credit card required. All API endpoints work on the free tier, though you're limited to Best Effort speed without access to Ludicrous Speed tiers. Failed searches receive automatic refunds, so the 250-search limit only counts successful requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (6.0/10):&lt;/strong&gt; The 250 searches per month places SerpAPI mid-tier, more generous than Scrapingbee (66 total) but less than Tavily (1,000/month), Exa (2,000 one-time), or Scrapingdog (11,000 with promotions).&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose SerpAPI when you need breadth of coverage
&lt;/h2&gt;

&lt;p&gt;SerpAPI works best when you need access to multiple search engines or specialized platforms through one provider. SEO tools monitoring rankings across Google, Bing, and Yahoo avoid maintaining separate integrations. E-commerce price monitoring pulls structured data from Amazon, Walmart, and eBay. Academic research platforms integrate Google Scholar without parsing citation formats. The $15 per 1,000 searches pays for itself when infrastructure complexity is the bottleneck.&lt;/p&gt;

&lt;p&gt;Skip SerpAPI if you only need Google search on a tight budget. At $0.015 per search, you're paying 3-15x more than alternatives. AI applications requiring semantic search or embeddings should use Exa or Tavily instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exa
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; is a search API built for AI applications using a proprietary embeddings-based index. Instead of scraping Google or Bing, Exa maintains its own crawl optimized for semantic search.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywh3afpkf3nthiczelax.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywh3afpkf3nthiczelax.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.180s average response time with 100% success rate. Response times ranged from 1.003s to 2.120s. &lt;a href="https://exa.ai/blog/fastest-search-api" rel="noopener noreferrer"&gt;Exa's published benchmarks&lt;/a&gt; claim P50 latency below 425ms, though our numbers likely include network overhead and full result retrieval while their metrics measure API response only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (10.0/10):&lt;/strong&gt; At 1.180s average, Exa delivered the fastest response times among all tested APIs, making it ideal for latency-sensitive applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://exa.ai/pricing" rel="noopener noreferrer"&gt;Exa's pricing&lt;/a&gt; charges $5 per 1,000 searches for 1-25 results ($0.005 per search). Requesting 26-100 results jumps to $25 per 1,000 searches ($0.025 per search), a 5x increase. Additional endpoints cost separately: contents retrieval runs $1 per 1,000 pages, answers and research tasks run $5 per 1,000 calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (7.5/10):&lt;/strong&gt; At $5 per 1,000 searches, Exa sits in the affordable mid-tier, significantly cheaper than SerpAPI ($15) but more expensive than Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.exa.ai/reference/getting-started" rel="noopener noreferrer"&gt;Exa provides SDKs&lt;/a&gt; for Python (&lt;code&gt;exa-py&lt;/code&gt;) and JavaScript (&lt;code&gt;exa-js&lt;/code&gt;), both with full TypeScript definitions. Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exa_py&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Exa&lt;/span&gt;

&lt;span class="n"&gt;exa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Exa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://dashboard.exa.ai" rel="noopener noreferrer"&gt;Playground&lt;/a&gt; lets you test queries with controls for search type, filters, and AI features while generating code snippets. &lt;a href="https://docs.exa.ai" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; includes integration examples for LangChain, LlamaIndex, CrewAI, Anthropic, and OpenAI, plus an &lt;a href="https://docs.exa.ai/mcp" rel="noopener noreferrer"&gt;MCP integration&lt;/a&gt; for Cursor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhniscvv9bxrz1aeafufu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhniscvv9bxrz1aeafufu.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (9.0/10):&lt;/strong&gt; Two official SDKs with TypeScript support, interactive playground, comprehensive docs, and MCP integration provide excellent developer experience, just slightly behind SerpAPI's eight-language SDK coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;Exa maintains a &lt;a href="https://exa.ai/exa-api" rel="noopener noreferrer"&gt;proprietary search index&lt;/a&gt; built for AI applications rather than scraping Google or Bing. The embeddings-based index covers web content, research papers, news, GitHub repositories, tweets, and PDFs with &lt;a href="https://docs.exa.ai/reference/search" rel="noopener noreferrer"&gt;category filters&lt;/a&gt; and date filtering. The &lt;a href="https://docs.exa.ai/reference/search" rel="noopener noreferrer"&gt;API&lt;/a&gt; provides multiple search modes (neural for semantic matching, keyword for traditional results, auto for intelligent combination) plus endpoints for content retrieval, similarity search, and automated research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (8.0/10):&lt;/strong&gt; Proprietary AI-powered semantic search offers unique capabilities for meaning-based queries, though it can't replicate specific search engine results like SerpAPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides $10 in credits with no expiration and no credit card requirement. At $5 per 1,000 searches for standard queries (1-25 results), this translates to 2,000 free searches. You can allocate credits across other endpoints (research/answers at 2,000 calls, content retrieval at 10,000 pages). All features work on the free tier including semantic search, similarity search, and AI-powered summaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (9.0/10):&lt;/strong&gt; 2,000 one-time free searches with no expiration places Exa among the most generous free tiers, second only to Scrapingdog's 11,000 credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Exa when you need semantic search
&lt;/h2&gt;

&lt;p&gt;Exa works best for AI agents, RAG systems, or LLM-powered research tools that benefit from meaning-based search. Voice AI companions leverage sub-2-second response times for fast web retrieval. Research platforms use the structured research endpoint to generate reports with citations. Chatbots get clean, parsed HTML without dealing with CAPTCHA.&lt;/p&gt;

&lt;p&gt;Skip Exa if you need exact Google SERP replication for SEO monitoring or Google Shopping data for price comparison. Watch the 5x price jump when requesting 26-100 results per query ($0.025 per search approaches premium pricing without multi-engine coverage).&lt;/p&gt;

&lt;h1&gt;
  
  
  Tavily
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; is a search API optimized for LLMs and RAG applications. Instead of returning URLs with snippets, Tavily aggregates content from up to 20 sources per query, ranks them using proprietary AI, and delivers parsed content ready for LLM consumption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuhcznnyyfpfabxeh88y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuhcznnyyfpfabxeh88y.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.885s average response time with 100% success rate. Response times ranged from 1.583s to 2.897s. The response time reflects that Tavily fetches, parses, and ranks content before responding, while traditional SERP APIs return URLs and leave content extraction to you. &lt;a href="https://blog.tavily.com/tavily-evaluation-part-1-tavily-achieves-sota-on-simpleqa-benchmark/" rel="noopener noreferrer"&gt;Tavily's published benchmarks&lt;/a&gt; focus on accuracy (93.3% on SimpleQA) rather than raw speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (8.5/10):&lt;/strong&gt; At 1.885s average, Tavily ranks third among tested APIs, delivering strong performance considering it aggregates and parses content from multiple sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tavily.com/#pricing" rel="noopener noreferrer"&gt;Tavily's pricing&lt;/a&gt; uses a credit-based system. Pay-as-you-go charges $0.008 per credit. Basic search consumes 1 credit per request ($8 per 1,000 searches), while advanced search uses 2 credits. Subscription tiers start at $30/month for 4,000 credits ($0.0075 per credit) and scale down to $0.005 per credit on the Growth plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (7.0/10):&lt;/strong&gt; At $8 per 1,000 searches on pay-as-you-go, Tavily offers mid-tier pricing between premium options like SerpAPI ($15) and budget options like Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Tavily provides SDKs&lt;/a&gt; for Python (&lt;code&gt;tavily-python&lt;/code&gt;) and JavaScript/TypeScript (&lt;code&gt;@tavily/core&lt;/code&gt;). Setup takes about 5 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tavily&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TavilyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TavilyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tvly-YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://app.tavily.com" rel="noopener noreferrer"&gt;Tavily's Playground&lt;/a&gt; generates real-time code as you build queries. Adjust parameters like search depth and result count, and the right panel updates with Python, JavaScript, or cURL code. &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt; includes integration guides for LangChain, LlamaIndex, OpenAI, Anthropic, Vercel AI, and Zapier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm4ch5gbrf6ee0wrwwb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbm4ch5gbrf6ee0wrwwb6.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Score (8.5/10):&lt;/strong&gt; Two official SDKs, real-time code generation in the playground, comprehensive integration guides, and MCP support provide strong developer experience, comparable to Exa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;Tavily uses &lt;a href="https://docs.tavily.com/documentation/about" rel="noopener noreferrer"&gt;proprietary AI ranking&lt;/a&gt; to aggregate content from up to 20 sources per query. The API provides &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;four endpoints&lt;/a&gt;: search (factual queries with AI ranking), extract (clean content from URLs), map (page discovery), and crawl (combined mapping and extraction). Search supports &lt;a href="https://docs.tavily.com/documentation/api-reference/endpoint/search" rel="noopener noreferrer"&gt;topic filters&lt;/a&gt; (general, news, finance), time range filters (day, week, month, year), and domain inclusion/exclusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (7.5/10):&lt;/strong&gt; AI-powered content aggregation with 93.3% accuracy on SimpleQA benchmarks provides strong factual search, though it can't replicate specific search engine results like SerpAPI or offer semantic search like Exa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 credits per month with no credit card requirement. At 1 credit per basic search, this translates to 1,000 free searches monthly. You can allocate credits across advanced searches (2 credits), content extraction (1 credit per 5 URLs), or mapping (1 credit per 10 pages). All features work on the free tier with 100 requests per minute rate limiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (8.0/10):&lt;/strong&gt; 1,000 recurring monthly credits places Tavily among the more generous free tiers, better than SerpAPI (250/month) or Scrapingbee (66 total) but less than Exa (2,000 one-time) or Scrapingdog (11,000).&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Tavily for AI-optimized search
&lt;/h2&gt;

&lt;p&gt;Tavily fits RAG applications, AI agents, or LLM-powered assistants that need factual, grounded web information. Chatbots get ranked content with source citations, research automation tools use the crawl endpoint to map entire domains, and content aggregation platforms pull clean text without handling CAPTCHA. The 93.3% accuracy on factual benchmarks and automatic content filtering reduce manual validation needs.&lt;/p&gt;

&lt;p&gt;Skip Tavily for exact Google or Bing SERP layouts needed in SEO monitoring or structured shopping results for price comparison. Applications needing only URLs without content extraction should consider lighter-weight SERP APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scrapingdog
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; is a web scraping service offering SERP API access alongside scrapers for Amazon, LinkedIn Jobs, Instagram, and other platforms. The service provides a &lt;a href="https://www.scrapingdog.com/google-search-api/" rel="noopener noreferrer"&gt;Google Search API&lt;/a&gt; and a &lt;a href="https://www.scrapingdog.com/universal-search-api/" rel="noopener noreferrer"&gt;Universal Search API&lt;/a&gt; that aggregates and de-duplicates results across Google, Bing, Yahoo, and DuckDuckGo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeeligc1zn5tmq6gop4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzeeligc1zn5tmq6gop4z.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 1.700s average response time with 100% success rate. Response times ranged from 0.709s to 4.432s. The measured performance contradicts &lt;a href="https://www.scrapingdog.com/blog/serpapi-vs-serper-vs-scrapingdog/" rel="noopener noreferrer"&gt;Scrapingdog's published benchmarks&lt;/a&gt;, which claim 1.25 second response times for Google SERP requests, though our 1.700s average is reasonably close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (9.0/10):&lt;/strong&gt; At 1.700s average, Scrapingdog ranks second among tested APIs, delivering strong performance at the lowest price point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/pricing" rel="noopener noreferrer"&gt;Scrapingdog's pricing&lt;/a&gt; uses a credit-based system with monthly subscriptions. Plans start at $40/month for 200,000 credits (Light) and scale to $30,000/month for 1 billion credits (Nova Pro). At 1 credit per Google search, the cost per 1,000 searches ranges from $0.20 (Light) to under $0.06 at high-volume tiers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (10.0/10):&lt;/strong&gt; At $0.20-$0.06 per 1,000 searches, Scrapingdog offers the cheapest pricing among all tested APIs, making it ideal for high-volume projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/documentation" rel="noopener noreferrer"&gt;Scrapingdog's documentation&lt;/a&gt; covers authentication, error codes, and endpoint references, though some pages returned 404 errors during testing. The service lacks official SDKs with helper functions found in competing APIs, requiring direct REST API calls using standard HTTP libraries. The dashboard's request builder generates code in cURL, Python, Java, Node.js, Ruby, PHP, Go, and C# as you configure parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1u4e576lo7s8ayq6z3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwq1u4e576lo7s8ayq6z3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup takes approximately 5-10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.scrapingdog.com/google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Developer Experience Score (5.0/10):&lt;/strong&gt; No official SDKs, documentation gaps with 404 errors, and REST-only integration give Scrapingdog the weakest developer experience among tested APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com/google-search-api/" rel="noopener noreferrer"&gt;Scrapingdog's Google Search API&lt;/a&gt; provides access to Google web search with organic results, People Also Ask sections, related searches, and multiple search types (web, images, news, shopping). The &lt;a href="https://www.scrapingdog.com/universal-search-api/" rel="noopener noreferrer"&gt;Universal Search API&lt;/a&gt; aggregates and de-duplicates results from Google, Bing, Yahoo, and DuckDuckGo in a single request. Beyond search engines, Scrapingdog provides dedicated scrapers for Amazon, Walmart, eBay, LinkedIn Jobs, Instagram, and other platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (6.0/10):&lt;/strong&gt; Coverage of 4 search engines via Universal Search API with de-duplication provides decent breadth, though significantly less than SerpAPI's 20+ platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 request credits with no expiration and no credit card requirement. Optional &lt;a href="https://www.scrapingdog.com/dashboard" rel="noopener noreferrer"&gt;social media promotions&lt;/a&gt; add 4,000 credits for following on X and 6,000 for posting a LinkedIn review, bringing the total to 11,000 credits. Free tier users access all API endpoints including the Universal Search API, JavaScript rendering, geo-targeting, and AI extraction features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (10.0/10):&lt;/strong&gt; 11,000 total free credits (with promotions) is the most generous free tier among all tested APIs, ideal for extensive evaluation and small projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Scrapingdog for cost-optimized scraping
&lt;/h2&gt;

&lt;p&gt;Scrapingdog fits high-volume projects where per-request cost matters more than response time. Data collection pipelines requiring millions of monthly searches benefit from pricing that drops to $0.00006 per request at scale. Lead generation tools can consolidate Google search results alongside LinkedIn profiles, Instagram accounts, or e-commerce platforms through a single provider. The Universal Search API's de-duplication feature benefits SEO tools comparing rankings across multiple search engines without manual result matching.&lt;/p&gt;

&lt;p&gt;Skip Scrapingdog for projects requiring official SDKs and comprehensive documentation. The REST-only API and documentation gaps observed during testing make integration more challenging than competitors with polished SDKs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Scrapingbee
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; is a general-purpose web scraping service that includes Google Search API functionality. Acquired by &lt;a href="https://www.scrapingbee.com/blog/scrapingbee-acquisition/" rel="noopener noreferrer"&gt;Oxylabs in June 2025&lt;/a&gt;, it targets developers needing headless browser rendering, JavaScript execution, and proxy rotation across multiple websites including e-commerce platforms, social media sites, and search engines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e1eoslatihryqwhrv7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1e1eoslatihryqwhrv7f.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Our 50-query benchmark measured 7.257s average response time with 100% success rate. Response times ranged from 1.453s to 22.978s, creating a 16x performance spread that makes response time prediction difficult. &lt;a href="https://www.scrapingdog.com/blog/scrapingbee-vs-scraperapi-vs-scrapingdog/" rel="noopener noreferrer"&gt;Competitor benchmarks from Scrapingdog&lt;/a&gt; show 90% success rate with 16.28 second average response time for Google SERP requests, confirming high variability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed Score (2.0/10):&lt;/strong&gt; At 7.257s average, Scrapingbee delivered the slowest response times among all tested APIs, with extreme variability reflecting its positioning as a general web scraper rather than SERP-optimized service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/pricing/" rel="noopener noreferrer"&gt;Scrapingbee's pricing&lt;/a&gt; uses a credit-based system where Google Search API requests cost 15 credits per call. Monthly plans start at $49.99 for 250,000 credits. For 1,000 Google searches at 15 credits per request, costs range from $0.56 to $1.44 depending on tier. Variable credit costs create billing unpredictability as enabling premium proxies increases request cost from 5 to 25 credits (5x multiplier).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing Score (8.5/10):&lt;/strong&gt; At $0.56-$1.44 per 1,000 searches, Scrapingbee offers competitive pricing in the mid-to-low range, more affordable than SerpAPI ($15) or Exa ($5) but slightly more expensive than Scrapingdog ($0.20).&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Experience
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/documentation/" rel="noopener noreferrer"&gt;Scrapingbee's documentation&lt;/a&gt; provides endpoint references, authentication guides, and code examples across Python, Node.js, Java, Ruby, PHP, Go, and C#. The request builder interface shows real-time credit costs and generates code in multiple languages as you configure parameters. Scrapingbee provides &lt;a href="https://www.scrapingbee.com/documentation/" rel="noopener noreferrer"&gt;official Python and Node.js SDKs&lt;/a&gt; that wrap authentication and parameter formatting.&lt;/p&gt;

&lt;p&gt;Setup takes approximately 5-10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.scrapingbee.com/api/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coffee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic_results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Developer Experience Score (7.0/10):&lt;/strong&gt; Two official SDKs, comprehensive documentation, and a request builder with real-time cost estimation provide decent developer experience, though credits charged for failed requests create cost uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search Capabilities
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com/features/google/" rel="noopener noreferrer"&gt;Scrapingbee's Google Search API&lt;/a&gt; provides access to Google web search only with structured JSON including organic results, local results, ads, related queries, images, news results, knowledge graph data, and top stories. Scrapingbee supports &lt;a href="https://www.scrapingbee.com/documentation/google/" rel="noopener noreferrer"&gt;multiple Google search types&lt;/a&gt; including web, news, Maps, and image search. The platform provides dedicated scrapers for Amazon, Walmart, and ChatGPT API alongside general HTML scraping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Capabilities Score (4.0/10):&lt;/strong&gt; Google-only coverage limits Scrapingbee to a single search engine, significantly less than SerpAPI (20+ engines), Scrapingdog (4 engines), or the proprietary search capabilities of Exa and Tavily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free Tier Generosity
&lt;/h2&gt;

&lt;p&gt;The free tier provides 1,000 API credits with no credit card required and no time expiration. At 15 credits per Google search, this translates to approximately 66 free searches. The 1,000 credits cover diverse use cases including HTML scraping (5 credits), Amazon scraping (15 credits), and Walmart scraping (15 credits), with all platform features available including JavaScript rendering, geo-targeting, and premium proxies at higher credit costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free Tier Score (2.0/10):&lt;/strong&gt; 66 total free searches is the least generous free tier among all tested APIs, limiting evaluation and making it difficult to thoroughly test the service before committing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Scrapingbee for unified web scraping
&lt;/h2&gt;

&lt;p&gt;Scrapingbee fits projects requiring both SERP data and general web scraping in a unified platform. Teams already scraping e-commerce sites, social media profiles, or JavaScript-heavy web applications can consolidate Google search results through the same service. Projects with flexible latency requirements can tolerate 7.257-second average response times and high variability for data collection pipelines, batch processing systems, or research workflows prioritizing breadth over speed.&lt;/p&gt;

&lt;p&gt;Skip Scrapingbee for dedicated SERP API needs. The 66-search free tier limits evaluation, Google-only coverage restricts multi-engine analysis, and 7.257-second average response times with 23-second worst-case performance make it unsuitable for real-time voice assistants or live autocomplete suggestions.&lt;/p&gt;

&lt;h1&gt;
  
  
  SerpAPI vs Tavily
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; and &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; represent different approaches to SERP data. SerpAPI scrapes existing search engines like Google and Bing, returning their results as structured JSON. Tavily runs its own search engine that aggregates content from up to 20 sites and ranks it using proprietary AI. This architectural difference determines which one fits your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mirror Google or aggregate multiple sources?
&lt;/h2&gt;

&lt;p&gt;When you query &lt;a href="https://serpapi.com/search-api" rel="noopener noreferrer"&gt;SerpAPI for Google results&lt;/a&gt;, you get exactly what appears on Google with all its ranking signals, personalization, and algorithmic biases. When you query Amazon, you get Amazon's product listings ranked by Amazon's algorithm. This matters for SEO monitoring, competitor analysis, and applications that need to replicate what users see on specific platforms. &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI supports 20+ search engines&lt;/a&gt; with dedicated endpoints for each.&lt;/p&gt;

&lt;p&gt;Tavily doesn't scrape existing engines. It &lt;a href="https://docs.tavily.com/documentation/about" rel="noopener noreferrer"&gt;aggregates and ranks content&lt;/a&gt; from multiple sources per query, scoring relevance with AI and formatting results for LLM consumption. You can't get "Bing results" from Tavily because Tavily is the search engine. This matters for AI agents and RAG applications where answer accuracy matters more than which search engine provided it.&lt;/p&gt;

&lt;p&gt;Our benchmarks measured SerpAPI at 0.072 seconds average response time versus Tavily's 2.327 seconds. The 32x speed difference reflects Tavily's extra processing: searching up to 20 sites, applying AI ranking, extracting content, and formatting for LLM context windows. SerpAPI returns Google's results as-is with minimal overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose SerpAPI when you need specific search engines
&lt;/h2&gt;

&lt;p&gt;Build with &lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; when you need to replicate what users see on specific platforms. SEO monitoring tools that track keyword rankings across Google, Bing, and other engines require SerpAPI's multi-engine support. Price comparison tools need Amazon, eBay, and Walmart product listings. Academic research applications can use &lt;a href="https://serpapi.com/google-scholar-api" rel="noopener noreferrer"&gt;SerpAPI's Scholar endpoint&lt;/a&gt; instead of building separate scrapers.&lt;/p&gt;

&lt;p&gt;The 0.072-second response time fits latency-sensitive applications like browser extensions, real-time dashboards, and user-facing search features. When your application needs to feel instant, the 32x speed advantage matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose Tavily when you're feeding LLMs
&lt;/h2&gt;

&lt;p&gt;Build with &lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; when creating AI agents, RAG applications, or LLM-powered tools that need accurate information rather than specific search engine results. &lt;a href="https://blog.tavily.com/tavily-evaluation-part-1-tavily-achieves-sota-on-simpleqa-benchmark/" rel="noopener noreferrer"&gt;Tavily's 93.3% accuracy on SimpleQA&lt;/a&gt; and LLM-optimized formatting reduce the post-processing needed to integrate search into your agent's workflow.&lt;/p&gt;

&lt;p&gt;The multi-source aggregation helps when you care about answer quality over result provenance. Instead of scraping Google's top result and hoping it's accurate, Tavily retrieves up to 20 sources and ranks them by relevance. If your application shows users "here's what I found about X" rather than "here are Google's top 10 results for X," Tavily's AI-scored relevance provides better raw material.&lt;/p&gt;

&lt;p&gt;The 2.327-second response time becomes acceptable when the alternative is making multiple follow-up requests to scrape and parse content from traditional SERP results.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Choose the Right SERP API
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://serpapi.com" rel="noopener noreferrer"&gt;SerpAPI&lt;/a&gt; works best when you need exact Google (or Bing, or Amazon) results with sub-100ms response times. The $15 per 1,000 searches pays for 20+ search engine coverage and infrastructure that handles CAPTCHA solving automatically. SEO monitoring tools and price comparison platforms that need traditional SERP layouts benefit from the 0.072-second average we measured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://exa.ai" rel="noopener noreferrer"&gt;Exa&lt;/a&gt; fits AI applications needing semantic search over a proprietary index. The $5 per 1,000 searches includes embeddings and meaning-based matching that keyword APIs don't provide. At 1.189 seconds average, Exa delivers the fastest semantic search among tested options. The 2,000 free searches make evaluation straightforward.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tavily.com" rel="noopener noreferrer"&gt;Tavily&lt;/a&gt; targets RAG pipelines requiring factual, multi-source content. The service aggregates up to 20 sites per query and formats results for LLM consumption at $5-$8 per 1,000 searches. The 1,000 monthly free credits that reset support sustained prototyping. Choose Tavily when accuracy matters more than speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.scrapingdog.com" rel="noopener noreferrer"&gt;Scrapingdog&lt;/a&gt; offers the lowest per-request pricing at scale, dropping to $0.000058 per request for high-volume users. Our 3.762-second average contradicted their claimed 1.25-second benchmark, but the cost advantage persists for budget-focused projects. The Universal Search API de-duplicates across four engines in single requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.scrapingbee.com" rel="noopener noreferrer"&gt;Scrapingbee&lt;/a&gt; makes sense for teams already scraping e-commerce or general websites who want to consolidate SERP access through one provider. The 5.411-second average and high variability make it unsuitable for latency-sensitive applications. At 66 free searches versus competitors offering 250-2,000, the free tier limits evaluation depth.&lt;/p&gt;

&lt;h1&gt;
  
  
  Frequently Asked Questions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  What is a SERP API?
&lt;/h2&gt;

&lt;p&gt;A SERP API provides programmatic access to search engine results through structured JSON responses. Instead of manually searching Google and scraping HTML, you send an HTTP request with query parameters and receive formatted data including organic results, ads, knowledge panels, and related searches. These APIs handle CAPTCHA solving and proxy rotation automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are SERP APIs legal?
&lt;/h2&gt;

&lt;p&gt;SERP APIs operate in a legal gray area. In the United States, &lt;a href="https://en.wikipedia.org/wiki/HiQ_Labs_v._LinkedIn" rel="noopener noreferrer"&gt;hiQ Labs v. LinkedIn (2019)&lt;/a&gt; established that scraping publicly accessible data doesn't violate the Computer Fraud and Abuse Act. Search engine terms of service typically prohibit automated access, but enforcement focuses more on technical countermeasures than legal action. &lt;a href="https://serpapi.com/legal-shield" rel="noopener noreferrer"&gt;SerpAPI offers U.S. Legal Shield&lt;/a&gt; on paid plans, covering legal fees if you face action for scraping public search data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do SERP APIs avoid getting blocked?
&lt;/h2&gt;

&lt;p&gt;SERP APIs use rotating residential proxies, CAPTCHA solving services, and request pattern randomization to avoid detection. They distribute requests across thousands of IP addresses that appear as regular user traffic. When search engines implement new anti-bot measures, the APIs update their infrastructure to maintain access. This is why using an API makes more sense than building your own scraping system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I use SERP APIs for commercial projects?
&lt;/h2&gt;

&lt;p&gt;Yes, all five APIs support commercial use. Check your plan's terms of service for specific restrictions. Most APIs prohibit reselling raw SERP data to competitors but allow using the data in your own products. &lt;a href="https://serpapi.com/pricing" rel="noopener noreferrer"&gt;SerpAPI's pricing page&lt;/a&gt; and &lt;a href="https://docs.tavily.com" rel="noopener noreferrer"&gt;Tavily's documentation&lt;/a&gt; specify commercial terms. Free tiers typically allow commercial use with volume limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Methodology
&lt;/h2&gt;

&lt;p&gt;Back to comparison results&lt;/p&gt;

&lt;p&gt;We tested each API with fifty standardized queries: "python tutorials", "best coffee makers 2025", "machine learning", "weather forecast" etc. The Python script measured response time from request to complete JSON parsing and verified successful responses. Each test ran once per API to simulate real-world usage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_serpapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://serpapi.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SERPAPI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_exa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.exa.ai/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EXA_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;numResults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_tavily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.tavily.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TAVILY_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scrapingdog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.scrapingdog.com/google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SCRAPINGDOG_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_scrapingbee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;encoded_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;quote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://app.scrapingbee.com/api/v1/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SCRAPINGBEE_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;render_js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python tutorials&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best coffee makers 2025&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;machine learning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weather forecast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;electric cars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;react hooks tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best restaurants near me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data science courses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yoga poses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;home workout routines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;javascript async await&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docker compose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes tutorial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws lambda pricing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;terraform vs ansible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;best noise cancelling headphones&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meal prep ideas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sourdough recipe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running shoes 2025&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meditation techniques&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typescript generics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres indexing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis caching&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongodb aggregation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git rebase vs merge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vim shortcuts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linux commands&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;css flexbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tailwind css&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nextjs routing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;graphql vs rest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;microservices architecture&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api gateway&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oauth2 flow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jwt authentication&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password hashing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sql injection prevention&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xss attack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;csrf protection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seo best practices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sitemap xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google analytics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversion rate optimization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a/b testing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email marketing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;social media strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content marketing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project management tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agile methodology&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrum vs kanban&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;apis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SerpAPI&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_serpapi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Exa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_exa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tavily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_tavily&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scrapingdog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_scrapingdog&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scrapingbee&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test_scrapingbee&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_func&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Testing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/50: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s - &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;✓&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;✗&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/50: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; - ERROR: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;avg_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;success_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;successes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;fastest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;slowest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success_rate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;success_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fastest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fastest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slowest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slowest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;successful_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;successes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Results:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Average: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;avg_time&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Success Rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;success_rate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Fastest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fastest&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Slowest: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;slowest&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save results to JSON
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_results.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Print summary
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FINAL SUMMARY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;avg_time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s avg, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;success_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran tests with a 100Mbps connection. Network latency impacts absolute times but relative performance remains consistent. Your results may vary based on location, query complexity, and API server load.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How to create a timeline: Excel, Word, PowerPoint, Google Sheets, Preceden, and JavaScript</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Thu, 13 Nov 2025 07:54:12 +0000</pubDate>
      <link>https://dev.to/ritza/how-to-create-a-timeline-excel-word-powerpoint-google-sheets-preceden-and-javascript-3126</link>
      <guid>https://dev.to/ritza/how-to-create-a-timeline-excel-word-powerpoint-google-sheets-preceden-and-javascript-3126</guid>
      <description>&lt;p&gt;A timeline visualizes events chronologically, making it easier to understand project progress, historical sequences, or future plans. Whether you're tracking project milestones, creating a presentation, or documenting company history, you need the right tool to build your timeline.&lt;/p&gt;

&lt;p&gt;This guide shows you how to create timelines using tools you likely already have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt;: Using SmartArt for basic document timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt;: Using SmartArt for report and document timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt;: Using SmartArt for presentation timelines.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt;/&lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;: Using Timeline view for collaborative projects (requires &lt;a href="https://workspace.google.com/" rel="noopener noreferrer"&gt;Google Workspace&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt;: Using dedicated timeline software for professional results.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;: Using &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; to build custom timeline charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each section is self-contained, so you can jump directly to the tool you want to use. Before diving into the how-to sections, you'll learn the key differences between timelines and Gantt charts to choose the proper visualization for your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Differences Between a Timeline and a Gantt?
&lt;/h2&gt;

&lt;p&gt;Timelines and Gantt charts both visualize events over time, but they serve different purposes. Understanding the difference helps you choose the right tool for your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a Timeline?
&lt;/h3&gt;

&lt;p&gt;A timeline is a linear visualization that shows events in chronological order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91bejph36obbiebl72tj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F91bejph36obbiebl72tj.png" alt=" " width="800" height="531"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Timelines focus on dates and milestones, making them ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Historical events such as company founding, product launches, and industry evolution.&lt;/li&gt;
&lt;li&gt;Project milestones like major deadlines and deliverables.&lt;/li&gt;
&lt;li&gt;Event planning, like conference agendas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A timeline answers the question: &lt;strong&gt;What happened when?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a Gantt Chart?
&lt;/h3&gt;

&lt;p&gt;A Gantt chart shows when events happen, how long they take, and how they relate to each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5l9xz1lx2ozqcsajvi2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5l9xz1lx2ozqcsajvi2.png" alt=" " width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gantt charts display:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Task start and end dates.&lt;/li&gt;
&lt;li&gt;Task duration.&lt;/li&gt;
&lt;li&gt;Overlapping tasks.&lt;/li&gt;
&lt;li&gt;Task dependencies – which tasks must finish before others can start.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Gantt chart answers the questions: &lt;strong&gt;When does this happen, how long does it take, and what depends on it?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeline vs Gantt Chart: Which One Should You Use?
&lt;/h3&gt;

&lt;p&gt;Choose a &lt;strong&gt;timeline&lt;/strong&gt; when you want to communicate a sequence of events to an audience. Timelines work best for presentations, reports, and documentation where clarity matters more than detailed project management.&lt;/p&gt;

&lt;p&gt;Choose a &lt;strong&gt;Gantt chart&lt;/strong&gt; when you need to plan and track complex projects with multiple tasks, dependencies, and resources. Gantt charts work best for project managers coordinating teams and deliverables.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Excel?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt; doesn't have a dedicated timeline chart. You can create a basic timeline using &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics, which work in Excel desktop applications but not in &lt;a href="https://www.microsoft365.com/launch/excel" rel="noopener noreferrer"&gt;Excel Online&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;p&gt;On the Excel application, follow these steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert tab.&lt;/li&gt;
&lt;li&gt;Select the SmartArt option.&lt;/li&gt;
&lt;li&gt;Then select "Process". You will see many options to describe a process, a timeline. &lt;/li&gt;
&lt;li&gt;Choose the one that works for you, but for this tutorial, we will use the first one, "Basic process".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qv6y3f526md4p7kpom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0qv6y3f526md4p7kpom.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;Excel displays a timeline with placeholder text. Click each text box and type your event name and date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjai6ip5rznjwkrsgcb7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjai6ip5rznjwkrsgcb7.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, you can use the SmartArt text utility, by typing on the "Enter" button for a new line, or the "+" button. A new item will appear on the right of your timeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in PowerPoint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt; provides &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics that make timeline creation straightforward. This feature is available in both PowerPoint desktop and &lt;a href="https://www.microsoft365.com/launch/powerpoint" rel="noopener noreferrer"&gt;PowerPoint Online&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In your PowerPoint presentation, follow these steps:

&lt;ol&gt;
&lt;li&gt;Click the Insert tab.&lt;/li&gt;
&lt;li&gt;Click SmartArt.&lt;/li&gt;
&lt;li&gt;Select "Process" from the left panel. You'll see various process diagrams, including timeline options.&lt;/li&gt;
&lt;li&gt;Select the timeline style you prefer. For this guide, we'll use "Basic Process".&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahnoox126qtn39gn4hsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahnoox126qtn39gn4hsb.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;A timeline graphic will appear with text placeholders. Click on each text box and type your event information. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fithuvw9uvpgqauayxzld.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fithuvw9uvpgqauayxzld.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, you can use the SmartArt text utility by typing on the "Enter" button for a new line or the "+" button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles, or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Word?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt; provides &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt; graphics for creating basic timelines in your documents. This method works in Word desktop applications (not &lt;a href="https://www.microsoft365.com/launch/word" rel="noopener noreferrer"&gt;Word Online&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Insert SmartArt
&lt;/h3&gt;

&lt;p&gt;On the Word application, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert tab.&lt;/li&gt;
&lt;li&gt;Select the SmartArt option.&lt;/li&gt;
&lt;li&gt;Then select "Process". You will see many options to describe a process, including timeline layouts.&lt;/li&gt;
&lt;li&gt;Choose the one that works for you, but for this tutorial, we will use "Basic process".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n491xoda2nbaihxpxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5n491xoda2nbaihxpxo.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Your Events
&lt;/h3&gt;

&lt;p&gt;A timeline graphic will appear with text placeholders. Click on each text box and type your event information - include both the event name and date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiamrczjlx2vinzqtsjdn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiamrczjlx2vinzqtsjdn.png" alt=" " width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add more events, click the SmartArt border, then click the SmartArt Design tab &amp;gt; Add Shape. New boxes appear on your timeline.&lt;/p&gt;

&lt;p&gt;To remove events, click a box and press Delete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Format Your Timeline
&lt;/h3&gt;

&lt;p&gt;Customize your timeline using the SmartArt Design tab. You can change the colors, apply SmartArt styles, or change the layout. &lt;/p&gt;

&lt;p&gt;For detailed formatting, select individual text boxes and use the Format tab to change fill colors, borders, or text properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Google Docs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Google Docs&lt;/a&gt; doesn't have a built-in timeline feature. You can create a timeline in &lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; using Timeline view, then add it to your document as an image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Timeline view is only available for &lt;a href="https://workspace.google.com/" rel="noopener noreferrer"&gt;Google Workspace&lt;/a&gt; accounts (Business Starter, Business Standard, Business Plus, Enterprise, Education editions). Free Google accounts do not have access to this feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Prepare Your Data in Google Sheets
&lt;/h3&gt;

&lt;p&gt;Create a new Google Sheet and set up your timeline data in columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Card Title,Start Date,End Date,Card Detail
Research &amp;amp; Planning,1/15/2025,2/1/2025,Initial research phase
Design Phase,2/15/2025,3/15/2025,UI/UX design
Development,4/15/2025,6/15/2025,Build features
Testing,6/15/2025,7/1/2025,QA and bug fixes
Launch,7/15/2025,7/15/2025,Product launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr4z6vdb8ql6ne8huc0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzr4z6vdb8ql6ne8huc0x.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your dates in a standard format like 1/15/2025 or 01/15/2025. Google Sheets recognizes these as dates automatically and right-aligns them in the cells.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Insert Timeline View
&lt;/h3&gt;

&lt;p&gt;In your Google Sheet, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Insert menu.&lt;/li&gt;
&lt;li&gt;Click Timeline.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5ucaim7fpfplm5u6t1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5ucaim7fpfplm5u6t1k.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select your data range, including all columns with headers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jl1lgwr11djv0h3evbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8jl1lgwr11djv0h3evbi.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click Ok after validating the selection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Google Sheets creates an interactive timeline view showing your tasks as cards on a horizontal timeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21stnabvjgdy5y3ltbzs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21stnabvjgdy5y3ltbzs.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the right panel, you can customize the timeline by modifying the card title, the card color, or the display. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Embed in Google Docs
&lt;/h3&gt;

&lt;p&gt;Google Docs doesn't support embedding timelines directly from Google Sheets. To add your timeline to a document:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In Google Sheets, click File -&amp;gt; Download -&amp;gt; PDF Document.&lt;/li&gt;
&lt;li&gt;Convert the PDF to an image using a tool like &lt;a href="https://pdf2png.com" rel="noopener noreferrer"&gt;pdf2png.com&lt;/a&gt; or &lt;a href="https://cloudconvert.com/" rel="noopener noreferrer"&gt;CloudConvert&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In Google Docs, click Insert -&amp;gt; Image -&amp;gt; Upload from computer.&lt;/li&gt;
&lt;li&gt;Select your converted timeline image.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in Preceden?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt; is a dedicated timeline software explicitly designed for creating professional timelines. The free plan lets you create timelines manually, while paid plans allow you to import spreadsheet data for faster timeline creation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a new timeline
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;preceden.com&lt;/a&gt; and sign up for an account. On the dashboard, click the "+Create Manually" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3j1noowdreuod3ypv5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3j1noowdreuod3ypv5o.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter a name for your timeline and click "Create Timeline →".&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add events to your timeline
&lt;/h3&gt;

&lt;p&gt;Preceden displays your blank timeline. Click the "+ Add Event" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceduxerzaci0zdzmhzpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceduxerzaci0zdzmhzpf.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter your event details, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event name.&lt;/li&gt;
&lt;li&gt;Start date.&lt;/li&gt;
&lt;li&gt;End date (optional for milestone events).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgra2pk50rs6hsm621yap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgra2pk50rs6hsm621yap.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click Save. Your event appears on the timeline immediately.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayv7vt4cbq72ucd6dwzb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fayv7vt4cbq72ucd6dwzb.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repeat this process to add more events. Alternatively, click "Bulk Add" to enter multiple events at once using a spreadsheet-style interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Use the timeline
&lt;/h3&gt;

&lt;p&gt;Preceden allows you to export the timeline in many formats, such as PDF or Images, or embed it if you need to display the chart on a website. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89d8fysqoohtwzvk2l4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89d8fysqoohtwzvk2l4s.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Create a Timeline in JavaScript
&lt;/h2&gt;

&lt;p&gt;Creating timelines in web applications requires a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; library that handles rendering, interactivity, and data management. While you could build a timeline from scratch using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;HTML canvas&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/SVG" rel="noopener noreferrer"&gt;SVG&lt;/a&gt;, production-quality timelines need features like drag-and-drop, zoom controls, and responsive design.&lt;/p&gt;

&lt;p&gt;For this guide, you will learn how to create a timeline using the &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt; component, which has easy setup and provides complete timeline functionality. &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; offers trial packages so you can test the library before purchasing. If you're interested in other JavaScript timeline options, you can also check &lt;a href="https://visjs.github.io/vis-timeline/docs/timeline/" rel="noopener noreferrer"&gt;vis-timeline&lt;/a&gt; (open source) and &lt;a href="https://timeline.knightlab.com/" rel="noopener noreferrer"&gt;Timeline.js&lt;/a&gt; (free for storytelling timelines).&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before creating your timeline, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; and &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; are installed on your computer.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://bryntum.com/" rel="noopener noreferrer"&gt;Bryntum&lt;/a&gt; account (free trial available).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Initialize your project
&lt;/h3&gt;

&lt;p&gt;Create a new project directory and initialize an npm project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;timeline-project
&lt;span class="nb"&gt;cd &lt;/span&gt;timeline-project
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Set up npm authentication
&lt;/h3&gt;

&lt;p&gt;Bryntum packages require authentication. Follow the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#login" rel="noopener noreferrer"&gt;npm repository login guide&lt;/a&gt; to authenticate your npm client.&lt;/p&gt;

&lt;p&gt;Then configure npm to access Bryntum packages by following the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#configure-npm" rel="noopener noreferrer"&gt;npm configuration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Install Bryntum Scheduler
&lt;/h3&gt;

&lt;p&gt;Install Bryntum Scheduler. You can use the trial version or purchase a license. Follow the &lt;a href="https://bryntum.com/products/scheduler/docs/guide/Scheduler/npm-repository#installing-trial-packages" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; for your chosen version.&lt;/p&gt;

&lt;p&gt;For the trial version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @bryntum/scheduler@npm:@bryntum/scheduler-trial@6.3.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create your HTML file
&lt;/h3&gt;

&lt;p&gt;Inside the project directory, create an &lt;code&gt;index.html&lt;/code&gt; file with the timeline container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nx"&gt;DOCTYPE&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="nx"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UTF-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viewport&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;width=device-width, initial-scale=1.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Timeline&lt;/span&gt; &lt;span class="nx"&gt;Chart&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stylesheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node_modules/@bryntum/scheduler/scheduler.stockholm.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;apple&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BlinkMacSystemFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Segoe UI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sans&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;timeline&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="nf"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/style&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/head&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeline-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Create your JavaScript timeline
&lt;/h3&gt;

&lt;p&gt;Create an &lt;code&gt;app,js&lt;/code&gt; file with the following timeline configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;DateHelper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./node_modules/@bryntum/scheduler/scheduler.module.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Define your timeline events&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Research &amp;amp; Planning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Design Phase&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Development&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Testing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Launch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Create a single resource (timeline row)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Project Timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the timeline&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Scheduler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;appendTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timeline-container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;viewPreset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekAndMonth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;eventDrag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventResize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventEdit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;eventTooltip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rowHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;barMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Run your timeline
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;index.html&lt;/code&gt; in a web browser. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhtawheu2bqlavj896sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhtawheu2bqlavj896sm.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll see an interactive timeline where you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag events to change their dates.&lt;/li&gt;
&lt;li&gt;Resize events to adjust duration.&lt;/li&gt;
&lt;li&gt;Click events to edit details.&lt;/li&gt;
&lt;li&gt;Hover over events to see tooltips.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This guide showed you how to create timelines in &lt;a href="https://www.microsoft.com/en-us/microsoft-365/excel" rel="noopener noreferrer"&gt;Excel&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/microsoft-365/word" rel="noopener noreferrer"&gt;Word&lt;/a&gt;, &lt;a href="https://www.microsoft.com/en-us/microsoft-365/powerpoint" rel="noopener noreferrer"&gt;PowerPoint&lt;/a&gt;, &lt;a href="https://www.google.com/docs/about/" rel="noopener noreferrer"&gt;Google Docs&lt;/a&gt;, &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt;, and JavaScript using &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.google.com/sheets/about/" rel="noopener noreferrer"&gt;Google Sheets&lt;/a&gt; and &lt;a href="https://www.preceden.com" rel="noopener noreferrer"&gt;Preceden&lt;/a&gt; offer the best experience because they use your spreadsheet data directly and let you easily customize colors. Microsoft tools use &lt;a href="https://support.microsoft.com/en-us/office/create-a-smartart-graphic-from-scratch-fac94c93-500b-4a0a-97af-124040594842" rel="noopener noreferrer"&gt;SmartArt&lt;/a&gt;, which is simple to use but requires manual updates for each event.&lt;/p&gt;

&lt;p&gt;For occasional timelines in documents or presentations, SmartArt works fine. For professional timelines with many events or frequent updates, use Preceden. And if you're a developer building a dashboard or tool that needs embedded timelines, use &lt;a href="https://bryntum.com/products/scheduler-pro/" rel="noopener noreferrer"&gt;Bryntum Scheduler Pro&lt;/a&gt; for complete control over appearance and functionality.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>CRM app with Node.js, Replit, and MongoDB</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Tue, 20 Sep 2022 09:09:47 +0000</pubDate>
      <link>https://dev.to/ritza/crm-app-with-nodejs-replit-and-mongodb-3p48</link>
      <guid>https://dev.to/ritza/crm-app-with-nodejs-replit-and-mongodb-3p48</guid>
      <description>&lt;p&gt;In this tutorial, we'll use Node.js on Replit and a MongoDB database to build a basic &lt;a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; (Create, Read, Update, Delete) &lt;a href="https://en.wikipedia.org/wiki/Customer_relationship_management"&gt;CRM&lt;/a&gt; (Customer Relationship Management) application. A CRM lets you store information about customers to help you track the status of every customer relationship. The application will be able to store and edit customer details, as well as keep notes about them.&lt;/p&gt;

&lt;p&gt;This tutorial won't be covering the basics of Node.js, but each line of code will be explained in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up
&lt;/h2&gt;

&lt;p&gt;All the code for our application will be written and hosted on Replit, so you won't need to install any additional software on your computer.&lt;/p&gt;

&lt;p&gt;If you don't already have a &lt;a href="https://replit.com/signup"&gt;Replit&lt;/a&gt; account, create one now.&lt;/p&gt;

&lt;p&gt;Next, head over to &lt;a href="https://www.mongodb.com/cloud/atlas"&gt;MongoDB Atlas&lt;/a&gt; and hit the "Try free" button. You should then sign up, clicking the "Get started free" button to complete the process.&lt;/p&gt;

&lt;p&gt;MongoDB Atlas is a fully managed Database-as-a-Service. It provides a document database (often referred to as NoSQL), as opposed to a more traditional relational database like PostgreSQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a cluster
&lt;/h2&gt;

&lt;p&gt;Once you've signed up with MongoDB Atlas, under "Shared Clusters", press the "Create a Cluster" button.&lt;/p&gt;

&lt;p&gt;Select a provider and a region. For the purposes of this tutorial, we'll choose "Google Cloud Platform" as the provider and "Iowa (us-central1)" as the region, although it should work regardless of the provider and region. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tL99OwnB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/mongo-setup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tL99OwnB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/mongo-setup.png" alt="Cluster Region" width="880" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give your cluster a name. Note that once you've created your cluster, you won't be able to change the name. Click "Create Cluster". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wdIkS2PL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cluster-name.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wdIkS2PL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cluster-name.png" alt="Cluster Name" width="880" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a bit of time, your cluster will be created. Once it's available, click on “Database Access” under the Security heading in the left-hand column and then click "Add New Database User". You need a database user to store and retrieve data. Enter a username and password for the user and make a note of those details - you'll need them later. Select “Read and write to any database” as the user privilege. Hit "Add User" to complete this step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TPgZ7sUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/add-db-user.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TPgZ7sUp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/add-db-user.png" alt="Adding a New Database User" width="880" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you need to allow network access to the database. Click on "Network Access" in the left-hand column, and “Add IP Address”. Because we won't have a static IP from Replit, we're just going to allow access from anywhere - don't worry, the database is still secured with the username and password you created earlier. In the pop-up, click "Allow Access From Anywhere" and then "Confirm". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---69Iyyut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/whitelist-entry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---69Iyyut--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/whitelist-entry.png" alt="Allow Access From Anywhere" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now select "Database" under "Deployment" in the left-hand column. Click on "Connect" and select “Connect Your Application”. This will change the pop-up view. Copy the "Connection String" as you will need it shortly to connect to your database from Replit. It will look something like this: &lt;code&gt;mongodb+srv://&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;@cluster0-zrtwi.gcp.mongodb.net/test?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g1jmdi4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/db-connect-string.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g1jmdi4U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/db-connect-string.png" alt="Retrieve Your Connection String" width="880" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a repl and connecting to the database
&lt;/h2&gt;

&lt;p&gt;Now we need a Node.js repl to write the code necessary to connect to our shiny new database. Navigate to Replit and create a new repl, selecting "Node.js" as the language.&lt;/p&gt;

&lt;p&gt;A great thing about Replit is that it makes projects public by default. This makes it easy to share and it's great for collaboration and learning, but we have to be careful not to make our database credentials available on the open internet.&lt;/p&gt;

&lt;p&gt;To solve this problem, we'll use Replit's &lt;code&gt;environment variables&lt;/code&gt;, as we have done in previous tutorials. Replit allows us to set secret environment variables through the "Secrets (Environment variables)" menu option. &lt;/p&gt;

&lt;p&gt;Open the "Secrets" menu option. There you will be able to set environment variables for your repl. Set the key as the name of your environment variable to &lt;code&gt;MONGO_USERNAME&lt;/code&gt;. Set the value as your database username, then click "Add new secret". Then create a new key called &lt;code&gt;MONGO_PASSWORD&lt;/code&gt; with its value set to your database password, and click "Add new secret". It should look something like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TD4F-tPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/replit-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TD4F-tPv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/replit-secrets.png" alt="Set Secrets Key Value" width="514" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replace&lt;/strong&gt; &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; with your database username and password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that we have credentials set up for the database, we can move on to connecting to it in our code.&lt;/p&gt;

&lt;p&gt;MongoDB is kind enough to provide a client that we can use. To test out our database connection, we're going to insert some customer data into our database. In your &lt;code&gt;index.js&lt;/code&gt; file (created automatically and found under the Files pane), add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongodb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongo_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MONGO_USERNAME&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mongo_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MONGO_PASSWORD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mongodb+srv://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mongo_username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;mongo_password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@cluster0-zrtwi.gcp.mongodb.net/crmdb?retryWrites=true&amp;amp;w=majority`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MongoClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;useNewUrlParser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down to see what is going on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; adds the dependency for the MongoDB Client. Replit makes things easy by installing all the dependencies for us, so we don't have to use something like npm to do it manually.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Line 2 &amp;amp; 3&lt;/strong&gt;, we retrieve our MongoDB username and password from the environment variables that we set up earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; has a few very important details that we need to get right:

&lt;ul&gt;
&lt;li&gt;Replace the section between the &lt;code&gt;@&lt;/code&gt; and the next &lt;code&gt;/&lt;/code&gt; with the same section of your connection string from MongoDB that we copied earlier. You may notice the &lt;code&gt;${mongo_username}&lt;/code&gt; and &lt;code&gt;${mongo_password}&lt;/code&gt; before and after the colon near the beginning of the string. These are called template literals. Template literals allow us to put variables in a string, which Node.js will then replace with the actual values of the variables.&lt;/li&gt;
&lt;li&gt;Note &lt;code&gt;crmdb&lt;/code&gt; after the &lt;code&gt;/&lt;/code&gt; and before the &lt;code&gt;?&lt;/code&gt;. This will be the name of the database that we will be using. MongoDB creates the database for us if it doesn't exist. You can change this to whatever you want to name the database, but remember what you changed it to for future sections of this tutorial.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; creates the client that we will use to connect to the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Making a user interface to insert customer data
&lt;/h2&gt;

&lt;p&gt;We're going to make an HTML form that will capture the customer data and send it to our Replit code, which will then insert it into our database.&lt;/p&gt;

&lt;p&gt;In order to actually present and handle an HTML form, we need a way to process HTTP GET and POST requests. The easiest way to do this is to use a web application framework. A web application framework is designed to support the development of web applications - it gives you a standard way to build your application and lets you get to building your application fast without having to do the boilerplate code.&lt;/p&gt;

&lt;p&gt;A really simple, fast, and flexible Node.js web application framework is &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;, which provides a robust set of features for the development of web applications.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is add the dependencies we need. Right at the top of your &lt;code&gt;index.js&lt;/code&gt; file (above the MongoDB code), add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bodyParser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body-parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bodyParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; adds the dependency for Express. Replit will take care of installing it for us.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; creates a new Express app that will handle incoming requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; adds a dependency for &lt;code&gt;'body-parser'&lt;/code&gt;. This is needed for the Express server to be able to handle the data that the form will send, and give it to us in a useful format to use in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt; adds a dependency for a basic HTTP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6 &amp;amp; 7&lt;/strong&gt; tell the Express app which parsers to use on incoming data. This is needed to handle form data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, we need to add a way for the Express app to handle an incoming request and give it to us in the form we need. Add the following lines of code below the code you added above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app.get&lt;/code&gt; tells Express that we want it to handle a GET request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'/'&lt;/code&gt; tells Express that it should respond to GET requests sent to the root URL. A root URL looks something like &lt;code&gt;https://crm.hawkiesza.repl.co&lt;/code&gt; - note that there are no slashes after the URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'/create'&lt;/code&gt; tells Express that it should respond to GET requests sent to the &lt;code&gt;/create&lt;/code&gt; endpoint after the root URL, i.e. &lt;code&gt;https://crm.hawkiesza.repl.co/create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;res.sendFile&lt;/code&gt; tells Express to send the given file as a response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before the server will start receiving requests and sending responses, we need to tell it to run. Add the following code below the previous line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;listening on port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; tells Express to set the port number to either a number defined as an environment variable, or 5000 if no definition was made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2-4&lt;/strong&gt; tells the server to start listening for requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we have an Express server listening for requests, but we haven't yet built the form that it needs to send back if it receives a request.&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;index.html&lt;/code&gt; and paste the following code into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/create"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Create"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a straightforward bit of HTML that puts a single button on the page. When this button is clicked, it sends a GET request to &lt;code&gt;/create&lt;/code&gt;, which the server will then respond to according to the code that we wrote above - in our case, it will send back the &lt;code&gt;create.html&lt;/code&gt; file which we will define now.&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;create.html&lt;/code&gt; and paste the following into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Customer details&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/create"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer name *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"John Smith"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer address *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"address"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"42 Wallaby Way, Sydney"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"telephone"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer telephone *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"telephone"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"+275554202"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"note"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer note&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"note"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Needs a new pair of shoes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't go in-depth into the above HTML. It is a very basic form with four fields (name, address, telephone, note) and a submit button, which creates an interface that will look like the one below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3O1MQpO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3O1MQpO_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details.png" alt="Customer Details" width="344" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the user presses the submit button, a POST request is made to &lt;code&gt;/create&lt;/code&gt; with the data in the form - we still have to handle this request in our code as we're currently only handling a GET request to &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you click the "Run" button now, a new window should appear on the right that displays the "Create" button we defined just now in &lt;code&gt;index.html&lt;/code&gt;. To see the form, you can also navigate to &lt;code&gt;https://&amp;lt;repl_name&amp;gt;.&amp;lt;your_username&amp;gt;.repl.co&lt;/code&gt;, replacing  with whatever you named your repl (but with no underscores or spaces) and  with your repl username. You will be able to see this URL in the repl itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UFxApidL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/first-run.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UFxApidL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/first-run.png" alt="Run Your Application" width="880" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you select "Create" and then fill in the form and hit submit, you'll get a response that says&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;Cannot POST /create&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
. This is because we haven't added the code that handles the form POST request, so let's do that. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nlqFJSmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cannot-post.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nlqFJSmw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/cannot-post.png" alt="*Cannot POST/create*" width="880" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the following code into your &lt;code&gt;index.js&lt;/code&gt; file, below the &lt;code&gt;app.get&lt;/code&gt; entry that we made above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 customer inserted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; defines a new route that listens for an HTTP POST request at &lt;code&gt;/create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; connects to the database. This happens asynchronously, so we define a callback function that will be called once the connection is made.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; creates a new collection of customers. Collections in MongoDB are similar to tables in SQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; defines customer data that will be inserted into the collection. This is taken from the incoming request. The form data is parsed using the parsers that we defined earlier and is then placed in the &lt;code&gt;req.body&lt;/code&gt; variable for us to use in the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; inserts the customer data into the collection. This also happens asynchronously, and so we define another callback function that will get an error if an error occurred, or the response if everything happened successfully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; throws an error if the above insert had a problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 8&lt;/strong&gt; gives us some feedback that the insert happened successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run the repl now (you may need to refresh it) and submit the filled-in form, you'll get a message back that says "Customer created". If you look in your cluster in MongoDB and select the "Collections" button, you'll see a document has been created with the details that we submitted in the form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TFoB1huy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TFoB1huy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-created.png" alt="Customer Created" width="880" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating and deleting database entries
&lt;/h2&gt;

&lt;p&gt;As a final step in this tutorial, we want to be able to update and delete database documents in our collection. To make things simpler, we're going to make a new HTML page where we can request a document and then update or delete it.&lt;/p&gt;

&lt;p&gt;First, let's make the routes to our new page. In your &lt;code&gt;index.js&lt;/code&gt;, add the following code below the rest of your routing code (i.e. before the MongoDB code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/get-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldnote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1-3&lt;/strong&gt; tells Express to respond to incoming GET requests on &lt;code&gt;/get&lt;/code&gt; by sending the &lt;code&gt;get.html&lt;/code&gt; file which we will define below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5-12&lt;/strong&gt; tells Express to respond to incoming GET requests on &lt;code&gt;/get-client&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; makes a call to the database to fetch a customer by name. If there is more than one customer with the same name, then the first one found will be returned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 9&lt;/strong&gt; tells Express to render the &lt;code&gt;update&lt;/code&gt; template, replacing variables with the given values as it goes. Important to note here is that we are also replacing values in the hidden form fields we created earlier with the current values of the customer details. This is to ensure that we update or delete the correct customer.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In your &lt;code&gt;index.html&lt;/code&gt; file, add the following code after the &lt;code&gt;&amp;lt;/form&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/get"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Update/Delete"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds a new button that will make a GET request to &lt;code&gt;/get&lt;/code&gt;, which will then return &lt;code&gt;get.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--14QLuuuN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/buttons.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--14QLuuuN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/buttons.png" alt="Index" width="880" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make a new file called &lt;code&gt;get.html&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt;  &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/get-client"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Customer name *&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"textInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"John Smith"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Get customer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes a simple form with an input for the customer's name and a button. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0gx6EYW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/get-customer.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0gx6EYW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/get-customer.png" alt="Get Customer" width="880" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking this button will make a GET request to &lt;code&gt;/get-client&lt;/code&gt;, which will respond with the client details, and we will be able to update or delete them.&lt;/p&gt;

&lt;p&gt;To see the customer details on a form after requesting them, we need a templating engine to render them onto the HTML page and send the rendered page back to us. With a templating engine, you define a template - a page with variables in it - and then give it the values you want to fill into the variables. In our case, we're going to request the customer details from the database and tell the templating engine to render them onto the page.&lt;/p&gt;

&lt;p&gt;We're going to use a templating engine called &lt;a href="https://pugjs.org/api/getting-started.html"&gt;Pug&lt;/a&gt;. Pug is a simple templating engine that integrates fully with Express. The syntax that Pug uses is very similar to HTML. One important difference in the syntax is that spacing is very important as it determines your parent/child hierarchy.&lt;/p&gt;

&lt;p&gt;First, we need to tell Express which templating engine to use and where to find our templates. Put the following line above your route definitions (i.e. after the other &lt;code&gt;app.use&lt;/code&gt; lines in &lt;code&gt;index.js&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;__express&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;views&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a new file called &lt;code&gt;update.pug&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;html
  body
    p #{message}
    h2= 'Customer details'
    form(method='POST' action='/update')
      input(type='hidden' id='oldname' name='oldname' value=oldname)
      input(type='hidden' id='oldaddress' name='oldaddress' value=oldaddress)
      input(type='hidden' id='oldtelephone' name='oldtelephone' value=oldtelephone)
      input(type='hidden' id='oldnote' name='oldnote' value=oldnote)
      label(for='name') Customer name:
      br
      input(type='text', placeholder='John Smith' name='name' value=name)
      br
      label(for='address') Customer address:
      br
      input(type='text', placeholder='42 Wallaby Way, Sydney' name='address' value=address)
      br
      label(for='telephone') Customer telephone:
      br
      input(type='text', placeholder='+275554202' name='telephone' value=telephone)
      br
      label(for='note') Customer note:
      br
      input(type='text', placeholder='Likes unicorns' name='note' value=note)
      br
      button(type='submit' formaction="/update") Update
      button(type='submit' formaction="/delete") Delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is very similar to the HTML form we created previously for &lt;code&gt;create.html&lt;/code&gt;, however this is written in the Pug templating language. We're creating a hidden element to store the "old" name, telephone, address, and note of the customer - this is for when we want to do an update.&lt;/p&gt;

&lt;p&gt;Using the old details to update the customer is an easy solution, but not the best solution as it makes the query cumbersome and slow. If you add extra fields into your database, you would have to remember to update your query as well, otherwise it could lead to updating or deleting the wrong customer if they have the same information. A better but more complicated way is to use the unique ID of the database document, as that will only ever refer to one customer.&lt;/p&gt;

&lt;p&gt;We have also put in placeholder variables for name, address, telephone, and note, and we have given the form two buttons with different actions.&lt;/p&gt;

&lt;p&gt;If you now run the code, you will have an index page with two buttons. Pressing the "Update/Delete" button will take you to a new page that asks for a customer name. If you fill in the customer name and press "Get customer", a page will load with the customer's details and two buttons below, "Update" and "Delete". Make sure you enter a customer name you have entered before.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1oPAnO0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details-final.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1oPAnO0e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/crm-app-mongodb-nodejs/customer-details-final.png" alt="Update-Delete" width="880" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our next step is to add the "Update" and "Delete" functionality. Add the following code below your routes in &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldnote&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newvalues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newvalues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 document updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Customer updated!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldaddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldtelephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;oldnote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;telephone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;telephone&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crmdb&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1 document deleted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Customer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; deleted`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This introduces two new POST handlers - one for &lt;code&gt;/update&lt;/code&gt;, and one for &lt;code&gt;/delete&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt; connects to our MongoDB database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt; throws an error if there was a problem connecting to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt; defines a query that we will use to find the document to update. In this case, we'll use the details of the customer &lt;em&gt;before&lt;/em&gt; it was updated. We saved this name earlier in a hidden field in the HTML. Trying to find the customer by its updated name obviously won't work, because it hasn't been updated yet. Also, note that we are setting some of the fields to null if they are empty. This is so that the database returns the correct document when we update or delete - if we search for a document that has no address with an address of '' (empty string), then our query won't return anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt; defines the new values that we want to update our customer with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 6&lt;/strong&gt; updates the customer with the new values using the query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 7&lt;/strong&gt; throws an error if there was a problem with the update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 8&lt;/strong&gt; logs that a document was updated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 9&lt;/strong&gt; re-renders the update page with a message saying that the customer was updated, and displays the new values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 15&lt;/strong&gt; connects to our MongoDB database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 16&lt;/strong&gt; throws an error if there was a problem connecting to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 17&lt;/strong&gt; defines a query that we will use to find the document to delete. In this case, we use all the details of the customer &lt;em&gt;before&lt;/em&gt; any changes were made on the form to make sure we delete the correct customer.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Line 18&lt;/strong&gt;, we connect to the database and delete the customer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 19&lt;/strong&gt; throws an error if there was a problem with the delete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 20&lt;/strong&gt; logs that a document was deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 21&lt;/strong&gt; sends a response to say that the customer was deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;If you run your application now, you'll be able to create, update, and delete documents in a MongoDB database. This is a very basic CRUD application, with a very basic and unstyled UI, but it should give you the foundation to build much more sophisticated applications.&lt;/p&gt;

&lt;p&gt;Some ideas for this are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You could add fields to the database to classify customers according to which stage they are in your sales &lt;a href="https://www.bitrix24.com/glossary/what-is-pipeline-management-definition-crm.php"&gt;pipeline&lt;/a&gt; so that you can track if a customer is potentially stuck somewhere and contact them to re-engage.&lt;/li&gt;
&lt;li&gt;You could integrate some basic marketing automation with a page allowing you to send an email or SMS to customers (though don't spam clients!).&lt;/li&gt;
&lt;li&gt;You could also add fields to keep track of customer purchasing information so that you can see which products do well with which customers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to start from where this tutorial leaves off, fork the repl at &lt;a href="https://replit.com/@ritza/replcrm"&gt;https://replit.com/@ritza/replcrm&lt;/a&gt;.&lt;br&gt;
To get additional guidance from the Replit community, you can join Replit's Discord server by using this invite link &lt;a href="https://replit.com/discord"&gt;https://replit.com/discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/replcrm"&gt;https://replit.com/@ritza/replcrm&lt;/a&gt;&lt;/p&gt;

</description>
      <category>replit</category>
      <category>crm</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Discord bot with Node.js and Replit</title>
      <dc:creator>RitzaCo</dc:creator>
      <pubDate>Mon, 12 Sep 2022 08:31:21 +0000</pubDate>
      <link>https://dev.to/ritza/building-a-discord-bot-with-nodejs-and-replit-3i7f</link>
      <guid>https://dev.to/ritza/building-a-discord-bot-with-nodejs-and-replit-3i7f</guid>
      <description>&lt;p&gt;In this tutorial, we'll use &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt; and Node.js to build a Discord chatbot. The bot will be able to join a Discord server and respond to messages.&lt;/p&gt;

&lt;p&gt;If you prefer Python, here's a &lt;a href="https://docs.replit.com/tutorials/discord-role-bot"&gt;Python Discord bot tutorial&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You'll find it easier to follow along if you have some JavaScript knowledge and you should have used Discord or a similar app such as Skype or Telegram before. We won't be covering the very basics of Node.js, but we will explain each line of code in detail, so if you have any experience with programming, you should be able to follow along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview and requirements
&lt;/h2&gt;

&lt;p&gt;We'll be doing all of our coding through the Replit web IDE, and we'll host our bot with Replit too, so you won't need to install any additional software on your machine. For this tutorial you will need to create a &lt;a href="https://discordapp.com/"&gt;Discord&lt;/a&gt; account (if you already have one, you can skip this).&lt;/p&gt;

&lt;p&gt;We will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating an application and a bot user in your Discord account.&lt;/li&gt;
&lt;li&gt;Creating a server on Discord.&lt;/li&gt;
&lt;li&gt;Adding our bot to our Discord server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get through these admin steps first and then we can get to the fun part of coding our bot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a bot in Discord and getting a token
&lt;/h3&gt;

&lt;p&gt;You can sign up for a free account over at &lt;a href="https://discordapp.com/register"&gt;Discord&lt;/a&gt;, and you can download one of their desktop or mobile applications from &lt;a href="https://discordapp.com/"&gt;the Discord homepage&lt;/a&gt;. You can also use Discord in the browser.&lt;/p&gt;

&lt;p&gt;Once you have an account, you'll want to create a Discord application. Visit &lt;a href="https://discordapp.com/developers/applications/"&gt;the Discord developer's page&lt;/a&gt; and press the "New application" button, as in the image below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mP-eLC6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-discord-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mP-eLC6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-discord-app.png" alt="Creating a new Discord application" width="880" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill out a name for your bot and select "Create".&lt;/p&gt;

&lt;p&gt;The first thing to do on the next page is to note your Application ID, which you'll need to add the bot to the server. You can come back later and get it from this page, or copy it somewhere so you can find it when you need it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---7Hh2W6u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/app-id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---7Hh2W6u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/app-id.png" alt="Record your Client ID" width="880" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also rename the application and provide a description for your bot, then press "Save Changes".&lt;/p&gt;

&lt;p&gt;You have now created a Discord application. The next step is to add a bot to this application, so head over to the "Bot" tab using the menu on the left and press the "Add Bot" button, as shown below. Click "Yes, do it" when Discord asks if you're sure about bringing a new bot to life.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TjsRfs4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TjsRfs4---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot.png" alt="Adding a bot to our Discord Application" width="880" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last thing we'll need from our bot is a token. Anyone who has the bot's token can prove that they own the bot, so you'll need to be careful not to share this with anyone. You can get the token by pressing "Reset Token", and then copy it to your clipboard by pressing "Copy".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lmAN3B4p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/get-token.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lmAN3B4p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/get-token.png" alt="Generating a token for our Discord bot" width="880" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--66ITfYqh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/copy-token.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--66ITfYqh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/copy-token.png" alt="Copying a token for our Discord bot" width="880" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take note of your token or copy it to your clipboard, as we'll need to add it to our code soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Discord server
&lt;/h3&gt;

&lt;p&gt;If you don't have a Discord server to add your bot to, you can create one by either opening the desktop Discord application that you downloaded earlier or returning to the Discord home page in your browser. Press the "+" icon, as shown below, to create a server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bgu1mlYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/create-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bgu1mlYU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/create-server.png" alt="Creating a Discord server" width="880" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press "Create a server" in the screen that follows, and then give your server a name. Once the server is up and running, you can chat with yourself, or invite some friends to chat with you. Soon we'll invite our bot to chat with us as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your Discord bot to your Discord server
&lt;/h3&gt;

&lt;p&gt;Our Discord bot is still just a shell at this stage as we haven't written any code to allow it to do anything, but let's go ahead and add the bot to our Discord server anyway. To add a bot to your server, you'll need the Application ID from the "General Information" page that we looked at before when we created our ReplBotApplication (ie. the application ID, not the secret bot token).&lt;/p&gt;

&lt;p&gt;Create a URL that looks as follows, but using your application ID instead of mine at the end (the link calls the application ID "client_id"):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discordapp.com/api/oauth2/authorize?scope=bot&amp;amp;client_id=746269162917331028"&gt;https://discordapp.com/api/oauth2/authorize?scope=bot&amp;amp;client_id=746269162917331028&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the URL that you created in your web browser and you'll see a page similar to the following where you can choose which server to add your bot to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hfhc6xXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot-to-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hfhc6xXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/add-bot-to-server.png" alt="Authorizing our bot to join our server" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the server we created in the step before this and hit the "Authorize" button. After completing the captcha, you should get an in-app Discord notification telling you that your bot has joined your server.&lt;/p&gt;

&lt;p&gt;Now we can get to the fun part of building a brain for our bot!&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a repl and installing our Discord dependencies
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is create a Node.js repl to write the code for our Discord bot. Over at &lt;a href="https://replit.com"&gt;Replit&lt;/a&gt;, create a new repl, choosing "Node.js" as your language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---KovBJkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-repl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---KovBJkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/new-repl.png" alt="Create a new repl" width="880" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We don't need to reinvent the wheel as there is already a great Node wrapper for the Discord bot API called &lt;a href="https://discord.js.org/"&gt;discord.js&lt;/a&gt;. Normally we would install this third-party library through &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt;, but because we're using Replit, we can skip the installation. Our repl will automatically pull in all dependencies. &lt;/p&gt;

&lt;p&gt;In the default &lt;code&gt;index.js&lt;/code&gt; file that is included with your new repl, add the following line of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press the "Run" button and you should see Replit installing the Discord library in the output pane on the right, as in the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nY9RI2yy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/install-discord-js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nY9RI2yy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/install-discord-js.png" alt="Installing Discord.js in our Repl" width="880" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our bot is nearly ready to go - but we still need to plug in our secret token. This will authorize our code to control our bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up authorization for our bot
&lt;/h2&gt;

&lt;p&gt;By default, Replit code is public. This is great as it encourages collaboration and learning, but we need to be careful not to share our secret bot token (which gives anyone who has access to it full control of our bot).&lt;/p&gt;

&lt;p&gt;To get around the problem of needing to give our &lt;em&gt;code&lt;/em&gt; access to the token while allowing others to access our code but &lt;em&gt;not&lt;/em&gt; our token, we'll be using &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps"&gt;environment variables&lt;/a&gt;. Replit allows us to set secret environment variables through the "Secrets (Environment variables)" menu option. &lt;/p&gt;

&lt;p&gt;Open the "Secrets" menu option. There you will be able to set environment variables for your repl. Set the key as the name of your environment variable to &lt;code&gt;DISCORD_BOT_SECRET&lt;/code&gt;. Set the value as your bot's secret token (note that this is the second token that we got while setting up the bot -- different from the Application ID that we used to add our bot to our server). It should look something like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A3Aj3xQj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/replit-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A3Aj3xQj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/replit-secrets.png" alt="Set Secrets Key Value" width="880" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's make a Discord bot that repeats everything we say but in reverse. We can do this in just a few lines of code. In your &lt;code&gt;index.js&lt;/code&gt; file, add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Discord&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;intents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GUILDS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GUILD_MESSAGES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DISCORD_BOT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageCreate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's tear this apart line by line to see what it does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt; is the line we added earlier. This line tells Replit to install the third party library and brings it into this file so that we can use it.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 2&lt;/strong&gt;, we create a Discord &lt;code&gt;Client&lt;/code&gt;. We'll use this client to send commands to the Discord &lt;em&gt;server&lt;/em&gt; to control our bot and send it commands. &lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 3-8&lt;/strong&gt;, we provide the &lt;code&gt;intents&lt;/code&gt; of our &lt;code&gt;Client&lt;/code&gt;, these are provided to designate which events our bot will be able to receive.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 9&lt;/strong&gt;, we retrieve our secret token from the environment variables (which Replit sets from the "Secrets" menu). &lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;line 11&lt;/strong&gt;, we define an &lt;code&gt;event&lt;/code&gt; for our client, which defines how our bot should react to the &lt;code&gt;ready&lt;/code&gt; event. The Discord bot is going to run &lt;em&gt;asynchronously&lt;/em&gt;, which might be a bit confusing if you're used to running standard synchronous code. We won't go into asynchronous coding in-depth here, but if you're interested in what this is and why it's used, there's a good guide over at &lt;a href="https://blog.risingstack.com/node-hero-async-programming-in-node-js/"&gt;RisingStack&lt;/a&gt;. In short, instead of running the code in our file from top to bottom, we'll be running pieces of code in response to specific events.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;lines 12-14&lt;/strong&gt;, we define how our bot should respond to the &lt;code&gt;ready&lt;/code&gt; event, which is fired when our bot successfully joins a server. We instruct our bot to output some information server-side (i.e. it will be displayed in our repl's output, but not sent as a message through to Discord). We'll print a simple "I'm in" message to see that the bot is there and print our bot's username (if you're running multiple bots, this will make it easier to work out who's doing what). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines 16-20&lt;/strong&gt; are similar, but instead of responding to a &lt;code&gt;ready&lt;/code&gt; event, we tell our bot how to handle new messages. &lt;strong&gt;Line 17&lt;/strong&gt; says we only want to respond to messages that aren't from us (otherwise our bot will keep responding to himself -- you can remove this line to see why that's a problem), and &lt;strong&gt;line 18&lt;/strong&gt; says we'll send a new message to the same channel we received a message in (&lt;code&gt;msg.channel&lt;/code&gt;) and the content we'll send will be the same message that we received, but backwards. To reverse a string, we split it into its individual characters, reverse the resulting array, and then join it all back into a string again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last line fires up our bot and uses the token we loaded earlier to log into Discord.&lt;/p&gt;

&lt;p&gt;Press the "Run" button again and you should see your bot reporting a successful channel join in the repl output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YpQTLuT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/bot-join.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YpQTLuT0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/bot-join.png" alt="Repl output showing channel join" width="880" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open Discord, and from within the server we created earlier, you will be able to send a message (by typing into the box highlighted below) and see your bot respond!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BgOXMgH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/message-repl-bot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BgOXMgH1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/message-repl-bot.png" alt="Send a message to your bot" width="880" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bot responds each time, reversing the text we enter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6Z01OpuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/repl-bot-response.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6Z01OpuE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/repl-bot-response.png" alt="Our bot can talk!" width="880" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping our bot alive
&lt;/h2&gt;

&lt;p&gt;Your bot can now respond to messages, but only for as long as your repl is running. If you close your browser tab or shut down your computer, your bot will stop and no longer respond to messages on Discord. &lt;/p&gt;

&lt;p&gt;Replit will keep your code running after you close the browser tab only if you are running a web server. Our bot doesn't require an explicit web server to run, but we can create a server and run it in the background just to keep our repl alive. &lt;/p&gt;

&lt;p&gt;Create a new file in your project called &lt;code&gt;keep_alive.js&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I'm alive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We won't go over this in detail as it's not central to our bot, but here we start a web server that will return "I'm alive" if anyone visits it.&lt;/p&gt;

&lt;p&gt;In our &lt;code&gt;index.js&lt;/code&gt; file, we need to add a &lt;code&gt;require&lt;/code&gt; statement for this server at the top. Add the following line near the top of &lt;code&gt;index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keep_alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./keep_alive.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After doing this and hitting the green "Run" button again, you should see some changes to your repl. For one, you'll see a new pane in the top right that shows the web output from your server. We can see that visiting our repl now returns a basic web page showing the "I'm alive" string that we told our web server to return by default.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--94hOG4Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/keep-alive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--94hOG4Fj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://replit-docs-images.bardia.repl.co/images/tutorials/basic-discord-bot-nodejs/keep-alive.png" alt="Running a Node server in the background" width="880" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now your bot will stay alive even after closing your browser or shutting down your development machine. Replit will still clean up your server and kill your bot after about one hour of inactivity, so if you don't use your bot for a while, you'll have to log into your repl and start the bot up again. Alternatively, you can set up a third-party (free!) service like &lt;a href="https://uptimerobot.com/"&gt;Uptime Robot&lt;/a&gt;. Uptime Robot pings your site every five minutes to make sure it's still working -- usually to notify you of unexpected downtime, but in this case the constant pings have the side effect of keeping our repl alive as it will never go more than an hour without receiving any activity. Note that you need to select the "HTTP" option instead of the "Ping" option when setting up Uptime Robot, as Replit requires regular HTTP requests to keep your chatbot alive, or you can use an &lt;a href="https://docs.replit.com/hosting/enabling-always-on"&gt;Always-on&lt;/a&gt; repl to keep it running 24/7.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forking and extending our basic bot
&lt;/h2&gt;

&lt;p&gt;This is not a very useful bot as-is, but the possibilities are only limited by your creativity now! You can have your bot receive input from a user, process the input, and respond in any way you choose. In fact, with the basic input and output that we've demonstrated, we have most of the components of any modern computer, all of which are based on the &lt;a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture"&gt;Von Neumann architecture&lt;/a&gt; (we could easily add the missing memory by having our bot write to a file, or with a bit more effort link in a &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite database&lt;/a&gt; for persistent storage).&lt;/p&gt;

&lt;p&gt;If you followed along with this tutorial, you'll have your own basic repl bot to play around with and extend. If you were only reading, you can easily fork my bot at &lt;a href="https://replit.com/@ritza/discord-bot-node-1"&gt;https://replit.com/@ritza/discord-bot-node-1&lt;/a&gt; and extend it however you like (you'll need to add your own token and reset the secrets still). Happy hacking!&lt;/p&gt;

&lt;p&gt;If you're stuck for ideas, you could try link up your Discord bot to the &lt;a href="https://dev.twitch.tv/"&gt;Twitch API&lt;/a&gt; to get notified when your favorite streamers are online, or build a &lt;a href="https://en.wikipedia.org/wiki/Interactive_fiction"&gt;text adventure&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading:
&lt;/h2&gt;

&lt;p&gt;If you are interested, here's another Discord bot tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.replit.com/tutorials/discord-role-bot"&gt;Role assignment bot with Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://replit.com/@ritza/discord-bot-node-1"&gt;https://replit.com/@ritza/discord-bot-node-1&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>node</category>
      <category>replit</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
