<?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: Ruhan Khandakar</title>
    <description>The latest articles on DEV Community by Ruhan Khandakar (@ruhanrk).</description>
    <link>https://dev.to/ruhanrk</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%2F221539%2F46fdaf65-dd75-4533-8442-d917087dcbbf.jpg</url>
      <title>DEV Community: Ruhan Khandakar</title>
      <link>https://dev.to/ruhanrk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ruhanrk"/>
    <language>en</language>
    <item>
      <title>2x faster, 7x cheaper: your search router doesn't need a chat model</title>
      <dc:creator>Ruhan Khandakar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 12:47:38 +0000</pubDate>
      <link>https://dev.to/ruhanrk/2x-faster-7x-cheaper-your-search-router-doesnt-need-a-chat-model-51l9</link>
      <guid>https://dev.to/ruhanrk/2x-faster-7x-cheaper-your-search-router-doesnt-need-a-chat-model-51l9</guid>
      <description>&lt;p&gt;We recently added record search to Sapling RCM, our medical billing app. You press ⌘K, type something, hit Enter, and get results. The query might be a clinic name, patient name, claim number, payment ID, or something else. The results can come from eight different tables.&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%2Fz7jg2jlpwby5jadvnyla.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%2Fz7jg2jlpwby5jadvnyla.png" alt="⌘K search showing a clinic, two enrollments and a claim" width="800" height="467"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Names are blurred.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before hitting the database, we run the search text through a small model. Its job is to figure out what kind of text the user entered, so we can search only the tables that make sense.&lt;/p&gt;

&lt;p&gt;I tried two models for this: Jev and Gemini 3.5 Flash-Lite. Then I measured how they compared on speed, cost, and missed results.&lt;/p&gt;

&lt;p&gt;Here's what the router does, how I tested it, and what I found.&lt;/p&gt;
&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Jev's median routing call took 466 ms. Gemini took 900 ms.&lt;/li&gt;
&lt;li&gt;Jev was about 7x cheaper: roughly $23 per million searches vs. $170.&lt;/li&gt;
&lt;li&gt;Jev missed results on 3 of 27 searches. Gemini missed on 6 of 30.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Ask what the text is, not which table to search
&lt;/h2&gt;

&lt;p&gt;One way to build this is to ask the model, "Which tables should I search?"&lt;/p&gt;

&lt;p&gt;We don't do that.&lt;/p&gt;

&lt;p&gt;Instead, we ask the model what kind of text the user entered, and then map that to tables in our own code.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind of text&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Tables we search&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;person name&lt;/td&gt;
&lt;td&gt;a patient or a provider&lt;/td&gt;
&lt;td&gt;patients, providers, claims&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim number&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLM-2026-00847&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;claims, payouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;payment ID&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ERA-…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;payments&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We have six kinds of text like this.&lt;/p&gt;

&lt;p&gt;This ended up being useful for a few reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each question is simple. "Could this be a person's name?" is easier than "Which of these eight tables should I search?"&lt;/li&gt;
&lt;li&gt;The model never needs to know our database schema. If we add another table, we update the mapping in our code instead of changing the prompt.&lt;/li&gt;
&lt;li&gt;A table can belong to multiple kinds. Claims, for example, can be found through a patient name or a claim number. The model doesn't need to know that relationship.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx2ie0h4b9g1cq0mulohe.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%2Fx2ie0h4b9g1cq0mulohe.png" alt="Searching a claim number returns only claims and payouts" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Router 1: Jev
&lt;/h2&gt;

&lt;p&gt;Jev, from TypeSafe and served through OpenRouter, isn't a chat model.&lt;/p&gt;

&lt;p&gt;You give it some state and a set of typed questions, and it returns a probability for each question. There's no generated text to parse and no reasoning tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://openrouter.ai/api/alpha/decisions&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &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;OPENROUTER_API_KEY&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;application/json&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;typesafe/jev-1.14&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;claim_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noul&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could this search text be all or part of a claim number? Claim numbers look like CLM-2026-00847.&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;// one question per kind&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;zdr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_500&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// { answers: { claim_number: { noul: 0.97 }, person_name: { noul: 0.02 }, ... } }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details in this request turned out to matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put your actual ID formats in the question. Otherwise, the model has to guess what your IDs look like.&lt;/li&gt;
&lt;li&gt;Pin the model version. Using something like &lt;code&gt;~latest&lt;/code&gt; means your routing behavior can change without you noticing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;provider: { zdr: true }&lt;/code&gt; tells OpenRouter to use providers with zero data retention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each kind, we search its tables when the score is 0.3 or higher. If nothing reaches 0.3, we take the highest scoring kind.&lt;/p&gt;

&lt;p&gt;The nice thing about getting probabilities is that the threshold is ours.&lt;/p&gt;

&lt;p&gt;If we're missing results, we can lower it and search more tables. If we're getting too many irrelevant results, we can raise it. We don't need to change the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Router 2: Gemini 3.5 Flash-Lite
&lt;/h2&gt;

&lt;p&gt;For Gemini, I gave it the same questions as a list in the system prompt, along with a JSON schema for the response. Temperature is set to 0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;vertex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-3.5-flash-lite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;contents&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="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;systemInstruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Decide which kinds of text this search could be. Return every kind that could apply.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;- claim_number: Could this be all or part of a claim number? ...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&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="na"&gt;responseMimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;responseJsonSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;array&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;enum&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="s1"&gt;person_name&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;claim_number&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;payment_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;required&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="s1"&gt;kinds&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;abortSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_500&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="c1"&gt;// {"kinds":["claim_number"]}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema makes the response easy to parse.&lt;/p&gt;

&lt;p&gt;The downside is that Gemini gives us a list rather than scores. There's no equivalent of "use everything above 0.3," so there's no threshold to tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let the router break search
&lt;/h2&gt;

&lt;p&gt;The router is an extra network call in the search path, so eventually it's going to fail.&lt;/p&gt;

&lt;p&gt;That's why every step has a fallback:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask Jev.&lt;/li&gt;
&lt;li&gt;If Jev fails for any reason missing API key, timeout, bad status, malformed response, etc. ask Gemini.&lt;/li&gt;
&lt;li&gt;If Gemini fails too, skip routing and search every table the user has access to.&lt;/li&gt;
&lt;li&gt;Otherwise, search only the tables for the selected kinds, in parallel.&lt;/li&gt;
&lt;li&gt;If those searches return nothing, search the remaining tables before showing "No results." The router can be wrong.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6403yvp46h5ua9jww9wt.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%2F6403yvp46h5ua9jww9wt.png" alt="Flow: search text goes to Jev, then Gemini if Jev fails, then all tables if both fail" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is important. Routing is an optimization. It shouldn't change whether search works at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace every call, but be careful with search text
&lt;/h2&gt;

&lt;p&gt;Every routing call creates an OpenTelemetry span with OpenInference attributes, so Phoenix displays it as an LLM call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startActiveSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search_routing&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;attributes&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="s1"&gt;openinference.span.kind&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;LLM&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;llm.provider&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;openrouter&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;llm.model_name&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;typesafe/jev-1.14&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;input.value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;span&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="c1"&gt;// ...call the model...&lt;/span&gt;
    &lt;span class="nx"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttributes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;output.value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llm.token_count.prompt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llm.token_count.completion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llm.cost.total&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cost&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;These spans made it easy to compare the two routers call by call.&lt;/p&gt;

&lt;p&gt;I also ran into two things worth mentioning.&lt;/p&gt;

&lt;p&gt;First, Phoenix displays any cost below one cent as &lt;code&gt;&amp;lt;$0.01&lt;/code&gt;. A Jev call costs only a few millionths of a dollar, so every call looked identical in the UI. If you care about the actual number, look at the raw &lt;code&gt;llm.cost.total&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Second, our Phoenix instance runs inside our own infrastructure, so the spans currently contain the actual search text. That's convenient for debugging, but it also means that search text can end up in your trace system.&lt;/p&gt;

&lt;p&gt;If you're using a SaaS trace provider, think about that before sending the raw input. In a healthcare app, the search text might be a patient name.&lt;/p&gt;

&lt;p&gt;Error logs can have the same problem.&lt;/p&gt;

&lt;p&gt;Some ORMs, in our case, Drizzle, include query parameters in database error messages. Since our query parameter is the user's search text, that can end up in the logs too.&lt;/p&gt;

&lt;p&gt;When a query fails, we log the database error code rather than the full error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I measured the two routers
&lt;/h2&gt;

&lt;p&gt;Speed and cost are straightforward. Accuracy is a little harder because we need to know what the correct answer actually was.&lt;/p&gt;

&lt;p&gt;I kept it simple: for every test query, I also searched all eight tables. If a router skipped a table that contained a match, I counted that search as a miss.&lt;/p&gt;

&lt;p&gt;The test queries included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a clinic name&lt;/li&gt;
&lt;li&gt;two claim numbers&lt;/li&gt;
&lt;li&gt;a payment ID and the bare prefix &lt;code&gt;ERA-&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the bare prefix of a batch number, &lt;code&gt;DISB-&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a provider ID (NPI)&lt;/li&gt;
&lt;li&gt;a short word: &lt;code&gt;pay&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a junk string: &lt;code&gt;a_b%&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;a common surname&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each query ran three times through our real search service.&lt;/p&gt;

&lt;p&gt;The surname only went to Gemini. We don't have a BAA with OpenRouter yet, so patient names don't go through it. That's why Jev has 27 calls while Gemini has 30.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&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;Jev 1.14&lt;/th&gt;
&lt;th&gt;Gemini 3.5 Flash-Lite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Routing time, median&lt;/td&gt;
&lt;td&gt;466 ms&lt;/td&gt;
&lt;td&gt;900 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing time, p95&lt;/td&gt;
&lt;td&gt;648 ms&lt;/td&gt;
&lt;td&gt;1,588 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per search&lt;/td&gt;
&lt;td&gt;$0.000023&lt;/td&gt;
&lt;td&gt;$0.00017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per 1M searches&lt;/td&gt;
&lt;td&gt;~$23&lt;/td&gt;
&lt;td&gt;~$170&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed calls&lt;/td&gt;
&lt;td&gt;0 of 27&lt;/td&gt;
&lt;td&gt;1 of 30 (timeout)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Searches that missed a result&lt;/td&gt;
&lt;td&gt;3 of 27&lt;/td&gt;
&lt;td&gt;6 of 30&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzadi81pnr119kt2ftqg6.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%2Fzadi81pnr119kt2ftqg6.png" alt="Routing time: Jev 466 ms median and 648 ms p95, Gemini 900 ms median and 1,588 ms p95." width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this test, Jev was roughly 2x faster at the median and 2.5x faster at p95. It was also about 7x cheaper and had fewer misses.&lt;/p&gt;

&lt;p&gt;Jev costs $0.042 per million input tokens, and output is free. Our questions are short, so each call ends up costing almost nothing.&lt;/p&gt;

&lt;p&gt;Gemini's actual cost may be slightly higher than what's shown in the table because our span didn't include thinking tokens.&lt;/p&gt;

&lt;p&gt;For our use case, Jev was the better fit of the two.&lt;/p&gt;

&lt;p&gt;The biggest difference wasn't just that it was faster and cheaper. The probability output gives us something we can tune. That's useful for a router because we don't really need the model to "answer" the user. We just need a good signal for deciding where to search.&lt;/p&gt;

&lt;p&gt;If you need a small classifier in a hot path, that's a pretty good reason not to reach for a chat model by default.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>jev</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Deploy Django + MySql Application into AWS EC2 instance with nginx and SSL</title>
      <dc:creator>Ruhan Khandakar</dc:creator>
      <pubDate>Thu, 13 May 2021 04:11:18 +0000</pubDate>
      <link>https://dev.to/ruhanrk/deploy-django-mysql-application-into-aws-ec2-instance-with-nginx-and-ssl-4f98</link>
      <guid>https://dev.to/ruhanrk/deploy-django-mysql-application-into-aws-ec2-instance-with-nginx-and-ssl-4f98</guid>
      <description>&lt;p&gt;All the version used in this tutorial&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.8.X&lt;/li&gt;
&lt;li&gt;Django 3.2.2&lt;/li&gt;
&lt;li&gt;gunicorn 20.1.0&lt;/li&gt;
&lt;li&gt;MySQL 8.0.20 (RDS)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  EC2 Setup
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create EC2 instance (Screenshots attached below), during this instance creation.
we'll have to download &lt;code&gt;pem/cer&lt;/code&gt; file. It'll be used later to access this instance from the terminal via SSH
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsepcum5zrogbcvfyytte.gif" alt="aws_ec2" width="800" height="441"&gt;
&lt;/li&gt;
&lt;li&gt;To connect the instance, Follow &lt;em&gt;&lt;em&gt;how to connect to instance guide&lt;/em&gt;&lt;/em&gt; from the EC2 dashboard.&lt;/li&gt;
&lt;li&gt;After login into our instance, run the following commands to update our instance -&amp;gt; &lt;code&gt;sudo apt update; sudo apt upgrade -y&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In order to use pip3 and venv we have to install some packages -&amp;gt; &lt;code&gt;sudo apt install python3-pip python3-venv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Let's create one virutal env -&amp;gt; &lt;code&gt;python3 -m venv env&lt;/code&gt;. And activate it -&amp;gt; &lt;code&gt;source env/bin/activate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Now we have to install &lt;code&gt;gunicorn&lt;/code&gt;&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;details&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt; (make sure you're in the virtual environment) -&lt;code&gt;pip3 install gunicorn&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: if your project has a MySQL database, you have to install some other packages&lt;br&gt;
&lt;code&gt;sudo apt install python3-dev default-libmysqlclient-dev build-essential libssl-dev&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Now let us pull the repository and install all the dependencies. After this do the necessary &lt;code&gt;migrations&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let us install nginx&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;details&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt; -&amp;gt; &lt;code&gt;sudo apt install nginx -y&lt;/code&gt;. now you can check with &lt;code&gt;sudo nginx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Before starting the &lt;code&gt;nginx&lt;/code&gt; setup we have to change our instance's  &lt;em&gt;&lt;em&gt;&lt;code&gt;security groups&lt;/code&gt;&lt;/em&gt;&lt;/em&gt; (Check below screenshots)&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.amazonaws.com%2Fuploads%2Farticles%2Fgpwy0c1466ey9okj07rx.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.amazonaws.com%2Fuploads%2Farticles%2Fgpwy0c1466ey9okj07rx.gif" alt="ec2_security_group" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now let us do a little test, we're going to start our application by gunicorn (what is it? will discuss it in later.) -&lt;code&gt;gunicorn --bind 0.0.0.0:8000 courier_allocation.wsgi:application&lt;/code&gt;, after running this we'll see something like 👇🏻&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.amazonaws.com%2Fuploads%2Farticles%2Fs4lmrdb1aat4p2bamby9.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%2Fs4lmrdb1aat4p2bamby9.png" alt="Screenshot 2021-05-09 at 4.57.28 PM" width="799" height="134"&gt;&lt;/a&gt; &lt;br&gt;
and now we can access our application from our instance IP address, like this &lt;code&gt;ec2-XX-XX-XX-XX.ap-south-1.compute.amazonaws.com:8000&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But now if we close our terminal then we won't be able to access our application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now we have to do some setup so that our application can run in the background. Here, we'll use &lt;code&gt;supervisor&lt;/code&gt;&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;details&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt;. To install this package we have to run &lt;code&gt;sudo apt install -y supervisor&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After installation, we have to create one supervisor config file. For this follow the below steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;First go to &lt;code&gt;cd /etc/supervisor/conf.d/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Here we'll create one config file -&amp;gt; &lt;code&gt;sudo touch gunicorn.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In this &lt;code&gt;conf&lt;/code&gt; file paste the below code.&lt;/li&gt;
&lt;/ol&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [program:gunicorn]
    directory=/home/ubuntu/PROJECT_FOLDER
    command=/home/ubuntu/env/bin/gunicorn --workers 3 --bind unix:/home/ubuntu/PROJECT_FOLDER/app.sock DJANGO_PROJECT_FOLDER.wsgi:application
    autostart=true
    autorestart=true
    stderr_logfile=/home/ubuntu/logs/gunicorn.err.log
    stdout_logfile=/home/ubuntu/logs/gunicorn.out.log
    [group:guni]
    programs:gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;




&lt;ul&gt;
&lt;li&gt;Now we have to run some commands&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;sudo supervisorctl reread&lt;/code&gt; -&amp;gt; Output - &lt;em&gt;&lt;em&gt;&lt;code&gt;guni: available&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo supervisorctl update&lt;/code&gt; -&amp;gt; Output -  &lt;em&gt;&lt;em&gt;&lt;code&gt;guni: added process group&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sudo supervisorctl status&lt;/code&gt; -&amp;gt; Output -  &lt;em&gt;&lt;em&gt;&lt;code&gt;guni:gunicorn                    RUNNING   pid 20199, uptime 0:00:17&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;




&lt;ul&gt;
&lt;li&gt;Now let's do the nginx&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;details&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt; configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;First we have to go to - &lt;code&gt;cd /etc/nginx/sites-available/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Here we need to create one config file - &lt;code&gt;sudo touch django.conf&lt;/code&gt; (you can choose any file name). Paste the below code inside your newly created file
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # Nginx Configuration for sample Django application
    ######################################################
    server {
        listen 80;
        server_name ec2-XX-XX-XX-XX.ap-south-1.compute.amazonaws.com;
        location / {
            include proxy_params;
            proxy_pass http://unix:/home/ubuntu/PROJECT_FOLDER/app.sock;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;After this run - &lt;code&gt;sudo ln django.conf /etc/nginx/sites-enabled/&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;now let's test - &lt;code&gt;sudo nginx -t&lt;/code&gt; - Output -&amp;gt; &lt;code&gt;nginx: the configuration file /etc/nginx/nginx.conf syntax is ok&lt;/code&gt; &lt;code&gt;nginx: configuration file /etc/nginx/nginx.conf test is successful&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sometime you might get this error -&amp;gt; &lt;code&gt;nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64&lt;/code&gt;. To fix this we have to add &lt;code&gt;server_names_hash_bucket_size  128;&lt;/code&gt; at the top of your http block (located in &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Now we have to restart our nginx service -&amp;gt; &lt;code&gt;sudo service nginx restart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;nginx&lt;/code&gt;&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;details&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt; is up and running 😁. You can directly access your app from Public IP Address (EC2)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: Still a lot of configuration is left, like cache, route multiple ports, load balancer etc. Will discuss it in a different post. 🙂&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  SSL Setup
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;* Requirements: domain address which is mapped to your Instance Public IP.&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup SSL certificate with certbot is quite easy. We just need to install some packages and a little bit of setup&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt; Install packages - &lt;code&gt;sudo apt install certbot python3-certbot-nginx&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Before we generate the certificate, we have to add our domain name in our &lt;code&gt;django.conf&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Change existing &lt;code&gt;server_name&lt;/code&gt; to - &lt;code&gt;server_name test.domain.com;&lt;/code&gt; (if you want to map multiple domains then use as space separate like this - &lt;code&gt;server_name test.domain.com testing.domain.com;&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;After setup is done, run the below two commands&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sudo certbot certonly --nginx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo certbot certonly --nginx&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Now we have to &lt;code&gt;restart&lt;/code&gt; our &lt;code&gt;nginx&lt;/code&gt; - &lt;code&gt;sudo service nginx restart&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Now you can visit your secure website 😊.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Django Static File issue Fix after deployment (production)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You might notice that in production, &lt;code&gt;CSS/JS/Images/Fonts&lt;/code&gt; files are not working for &lt;code&gt;/admin&lt;/code&gt;.&lt;br&gt;
This is because of the &lt;code&gt;Static&lt;/code&gt; folder issue. In the local development environment &lt;code&gt;Django&lt;/code&gt; use &lt;code&gt;Python HTTPServer&lt;/code&gt;, and it automatically handles &lt;code&gt;Static files&lt;/code&gt;. But in production our server is running through &lt;code&gt;nginx&lt;/code&gt;, that's why we have to do little setup. Let's do the setup.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;First, we have to create one &lt;code&gt;static&lt;/code&gt; folder in our &lt;code&gt;root&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Now inside our project main &lt;code&gt;settings.py&lt;/code&gt; we just need to add one line of code&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;STATIC_ROOT =  BASE_DIR / 'static/'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Now run this command &lt;code&gt;python3 manage.py collectstatic&lt;/code&gt;&lt;sup&gt;&lt;em&gt;&lt;em&gt;&lt;code&gt;3&lt;/code&gt;&lt;/em&gt;&lt;/em&gt;&lt;/sup&gt;. With this command, &lt;code&gt;Django&lt;/code&gt; will copy all static assets into your &lt;code&gt;Root Static Folder&lt;/code&gt;. In our case, it's the &lt;code&gt;Static&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now one setup is left, which is &lt;code&gt;nginx&lt;/code&gt;,&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Go to your &lt;code&gt;nginx&lt;/code&gt; &lt;code&gt;django.conf&lt;/code&gt; file and add the below code after the &lt;code&gt;location&lt;/code&gt; code block&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location /static/ {
               autoindex on;
               alias /home/ubuntu/PROJECT_FOLDER/static/;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Now restart &lt;code&gt;nginx&lt;/code&gt; server - &lt;code&gt;sudo service nginx restart&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  nginx
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Nginx&lt;/code&gt;, pronounced like “&lt;em&gt;&lt;em&gt;engine-ex&lt;/em&gt;&lt;/em&gt;”, is an open-source web server that, since its initial success as a web server, is now also used as a reverse proxy, HTTP cache, and load balancer.&lt;/p&gt;

&lt;p&gt;Nginx is built to offer low memory usage and high concurrency. Rather than creating new processes for each web request, Nginx uses an asynchronous, event-driven approach where requests are handled in a single thread.&lt;/p&gt;

&lt;p&gt;With Nginx, one master process can control multiple worker processes. The master maintains the worker processes, while the workers do the actual processing. Because Nginx is asynchronous, each request can be executed by the worker concurrently without blocking other requests. &lt;a href="https://kinsta.com/knowledgebase/what-is-nginx/#:~:text=Nginx%2C%20pronounced%20like%20%E2%80%9Cengine%2D,HTTP%20cache%2C%20and%20load%20balancer." rel="noopener noreferrer"&gt;more info&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  gunicorn:
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Gunicorn&lt;/em&gt; is a stand-alone &lt;em&gt;WSGI&lt;/em&gt; web application server that offers a lot of functionality. It natively supports various frameworks with its adapters, making it an extremely easy to use drop-in replacement for many development servers that are used during development.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  supervisor:
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;At some point you'll likely find yourself writing a script that needs to run all the time - a "long running script". These are scripts that shouldn't fail if there's an error or ones that should restart when the system reboots.&lt;/p&gt;

&lt;p&gt;To accomplish this, we need something to watch these scripts. Such tools are process watchers. They watch processes and restart them if they fail, and ensure they start on system boot.&lt;/p&gt;

&lt;p&gt;And &lt;em&gt;Supervisord&lt;/em&gt; is a simple and popular choice for doing these tasks and monitoring our process&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  collectstatic
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;Using the &lt;em&gt;collectstatic&lt;/em&gt; command, Django looks for all static files in your apps and collects them into a single location that can easily be served in production, i.e. the STATIC_ROOT. In our case, we are telling Django that when we run &lt;code&gt;python3 manage.py collectstatic&lt;/code&gt;, gather all static files into a folder called &lt;code&gt;static&lt;/code&gt; in our project root directory. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it for this article. I hope you found it useful. Feel free to drop your comments below and like the post.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>nginx</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
