<?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: Bruno Pérez</title>
    <description>The latest articles on DEV Community by Bruno Pérez (@bd_perez).</description>
    <link>https://dev.to/bd_perez</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F15686%2Fde232564-b2b5-4d4f-b9f4-a7114dddde81.jpg</url>
      <title>DEV Community: Bruno Pérez</title>
      <link>https://dev.to/bd_perez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bd_perez"/>
    <language>en</language>
    <item>
      <title>10 Ways To Reduce Your LLM API Costs</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Wed, 20 May 2026 16:02:25 +0000</pubDate>
      <link>https://dev.to/bd_perez/10-ways-to-reduce-your-llm-api-costs-2l33</link>
      <guid>https://dev.to/bd_perez/10-ways-to-reduce-your-llm-api-costs-2l33</guid>
      <description>&lt;p&gt;So you made it, you have your AI app on prod, you are onboarding your users and they like what you've done, cheers! Now comes the hard-to-swallow part, the AI bill.&lt;/p&gt;

&lt;p&gt;Serving those users consumes AI inference and &lt;strong&gt;it's literally eating all your margins&lt;/strong&gt;. Let's see 10 ways to reduce that AI bill of yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Choose a well-fitted AI model&lt;/li&gt;
&lt;li&gt;Use your Pro subscriptions&lt;/li&gt;
&lt;li&gt;Reduce output tokens to cut your LLM bill&lt;/li&gt;
&lt;li&gt;Use prompt caching when you can&lt;/li&gt;
&lt;li&gt;Use Batch API for nightly workflows&lt;/li&gt;
&lt;li&gt;Be FLEX-ible and accept slow tiers&lt;/li&gt;
&lt;li&gt;Don't use AI&lt;/li&gt;
&lt;li&gt;Use free models and free tiers&lt;/li&gt;
&lt;li&gt;Don't miss those Cloud provider credits&lt;/li&gt;
&lt;li&gt;Observe your AI costs and take back control&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Choose a well-fitted AI model
&lt;/h2&gt;

&lt;p&gt;Choosing the perfectly fitted model is not that easy. While realizing that your model is not "smart enough" for your requirements is the easy part, the other side is trickier: &lt;strong&gt;you may use an overkill model and you are overspending without noticing it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Potential reductions are huge here, for example switching from GPT-5.5 to GPT-5.4 reduces costs by 50%. Use GPT-5.4 Mini instead? That's 85% cheaper.&lt;/p&gt;

&lt;p&gt;However nothing is magical here: the cheaper models produce lower-quality outputs. Spending time to benchmark models on your particular use cases is the key to evaluate the impact of the quality loss. The closer you are to real production data, the better. Here are some ideas to put you on the right track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downshift within your model's family: flagship -&amp;gt; mini -&amp;gt; nano&lt;/li&gt;
&lt;li&gt;Look for other providers' models: For example Chinese providers tend to be cheaper for an equivalent quality&lt;/li&gt;
&lt;li&gt;Go for the previous generation: Opus 4.7 -&amp;gt; Opus 4.6 -&amp;gt; Opus 4.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In some cases, the query is variable and you can't anticipate it. Using &lt;a href="https://manifest.build/blog/what-is-an-llm-router/" rel="noopener noreferrer"&gt;LLM routers&lt;/a&gt; that evaluate your query on-the-fly can be a good choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Use your Pro subscriptions
&lt;/h2&gt;

&lt;p&gt;Are you paying for a ChatGPT, MiniMax or other pro subscription? Make the most of it and plug it into your app. With &lt;a href="https://manifest.build/" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt;, you can plug several pro subscriptions into your apps, while keeping your code and favorite SDK as it is. As of today Manifest allows you to plug Anthropic, GitHub Copilot, MiniMax, Ollama Cloud, OpenAI, OpenCode Go and Z.ai pro subscription directly into your AI app. Make sure it's ok with the terms of service of your providers.&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%2Fplph1eincqwtcqeimb71.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%2Fplph1eincqwtcqeimb71.png" alt="The Connect providers modal in Manifest — toggle Anthropic, GitHub Copilot, MiniMax, Ollama Cloud, OpenAI and other subscriptions on for routing" width="563" height="843"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The thing to watch here is the rate limit. &lt;strong&gt;Those subscriptions are often way cheaper than their API key alternative&lt;/strong&gt;, but they are limited in usage. You need to set up a &lt;a href="https://manifest.build/docs/fallback" rel="noopener noreferrer"&gt;fallback model&lt;/a&gt; in case you hit the API rate limit. See how &lt;a href="https://manifest.build/blog/mytrainer-manifest-routing/" rel="noopener noreferrer"&gt;MyTrainer did exactly this in production&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Reduce output tokens to cut your LLM bill
&lt;/h2&gt;

&lt;p&gt;Did you notice that you don't actually pay directly for the inference of the model you call? You pay for what goes in and for what comes out of it, not the "amount of thinking" itself. What if we can tweak the system to produce the same value with less?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The output tokens cost on average 5 times more than the input tokens.&lt;/strong&gt; It's totally worth it to spend more on the input tokens to reduce the output ones.&lt;/p&gt;

&lt;p&gt;The easiest way is to simply tell the LLM explicitly to "be concise" and even specify the format that you are expecting. Ask for structured JSON or CSV instead of prose. Prose tends to be long and adds verbosity to the outputs. If you need prose, &lt;a href="https://github.com/juliusbrussee/caveman" rel="noopener noreferrer"&gt;Caveman&lt;/a&gt; (60k+ GH stars) reduces output tokens by 75% by… speaking like a caveman. &lt;em&gt;Caveman tool real. Make agent talk short.&lt;/em&gt; 🪨&lt;/p&gt;

&lt;p&gt;By the way most providers allow a "max_tokens" that truncate outputs. While this doesn't make the model concise — it just truncates — it can still prevent long outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Use prompt caching when you can
&lt;/h2&gt;

&lt;p&gt;Caching has been used for decades to reduce compute and thus latency. For example when a website shows you a list of items, like the latest news for example, it will load from the database only for the first user, and then store it somewhere. Next users will receive that list from the cache directly, and have it served almost instantaneously.&lt;/p&gt;

&lt;p&gt;It works the same with LLM prompts: Models do heavy work on every token you send to them. If part of it doesn't change between requests, they do less work and you pay less. Generally cached input costs between 50% and 90% less.&lt;/p&gt;

&lt;p&gt;The #1 rule here is that &lt;strong&gt;your static content (the one that doesn't change) should come before the dynamic content (the volatile one)&lt;/strong&gt;. The first changed character and you're paying full price for following tokens. Structure your call correctly so that system prompts and knowledge bases come first:&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="n"&gt;messages&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;system&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;KNOWLEDGE_BASE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;# Static
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&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;user&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&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_question&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="c1"&gt;# Dynamic
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;resp&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="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.4-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&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;h2&gt;
  
  
  5. Use Batch API for nightly workflows
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Batch API is a straight 50% discount on inference if you accept to receive the response within 24 hours.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You're not going to implement that on a chat or on any actions that need real-time answers, however any background process is a good candidate. It requires a bit of change in your code but it's totally worth it if you have a significant amount of inference in scheduled tasks, nightly workflows or routines.&lt;/p&gt;

&lt;p&gt;Here is the link to the providers' docs that support batch API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/guides/batch" rel="noopener noreferrer"&gt;OpenAI Batch API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/build-with-claude/batch-processing" rel="noopener noreferrer"&gt;Anthropic Batch Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/batch-api?batch=file" rel="noopener noreferrer"&gt;Gemini Batch API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.together.ai/docs/inference/batch/overview" rel="noopener noreferrer"&gt;Together.ai Batch Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.groq.com/docs/batch" rel="noopener noreferrer"&gt;Groq Batch API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.alibabacloud.com/help/en/model-studio/batch-inference" rel="noopener noreferrer"&gt;Alibaba Cloud batch inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.kimi.ai/docs/guide/use-batch-api#using-batch-api-for-bulk-processing" rel="noopener noreferrer"&gt;Kimi Batch API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.mistral.ai/studio-api/batch-processing" rel="noopener noreferrer"&gt;Mistral Batch Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.fireworks.ai/guides/batch-inference" rel="noopener noreferrer"&gt;Fireworks Batch API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Be FLEX-ible and accept slow tiers
&lt;/h2&gt;

&lt;p&gt;Some providers even have a "flex" tier: a synchronous tier explicitly slower than the standard. Unlike Batch API, here you do get the real-time response, but it is significantly slower. However &lt;strong&gt;the discount is usually the same: 50% off&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now it's up to you to decide if you can afford that extra latency on some calls. Here is a list of providers that offer that trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/guides/flex-processing" rel="noopener noreferrer"&gt;OpenAI Flex processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html#w2aac28b5c11" rel="noopener noreferrer"&gt;Amazon Bedrock Flex tier&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Don't use AI
&lt;/h2&gt;

&lt;p&gt;This one can sound a bit provocative but the cheapest inference will always be the one you don't use. Are those AI features genuinely needed? Or are they just decorative nice-to-haves?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limiting LLM calls and using algorithms (aka good old software) when possible is the biggest win on this list.&lt;/strong&gt; Of course it's easier said than done, but many operations can be done programmatically: validation, regex, rules, heuristics…&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pro tip:&lt;/em&gt; go hybrid. Sometimes algorithms cannot handle all cases, but work well on some of them. Use conditions in your code to use them when possible, and fall back on LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Use free models and free tiers
&lt;/h2&gt;

&lt;p&gt;If you are just hacking around, &lt;strong&gt;maybe you don't need to pay at all!&lt;/strong&gt; Some providers offer inference for free on some models. Of course the rate limit is quite low but if your app has a low AI inference consumption, it may work. If it doesn't, you can still use &lt;a href="https://manifest.build/docs/fallback" rel="noopener noreferrer"&gt;model fallback&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mnfst/free-llm-apis" rel="noopener noreferrer"&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%2Fuod1i51dtse716svfpjo.png" alt="Awesome Free LLM APIs — a curated list of LLM providers with a permanent free tier" width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you know what? We prepared a very cool list of &lt;a href="https://github.com/mnfst/free-llm-apis" rel="noopener noreferrer"&gt;Awesome Free LLM APIs&lt;/a&gt; just for you 😎&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Don't miss those Cloud provider credits
&lt;/h2&gt;

&lt;p&gt;Building a startup? &lt;strong&gt;All major cloud providers can give you up to $300k in cloud credits&lt;/strong&gt;, most of them can be used in inference. The terms vary from one provider to another, and you probably need to fill applications and even meet them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/startups/credits/" rel="noopener noreferrer"&gt;AWS Activate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.microsoft.com/en-us/startups" rel="noopener noreferrer"&gt;Microsoft for Startups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;Google for Startups Cloud Program&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The concept here is that the faster you can burn those tokens, the more they'll offer you. It makes sense as they are looking for new potential big customers that will stick with their models.&lt;/p&gt;

&lt;p&gt;You'll probably never get the $300k on the first shot, it goes incrementally. One important thing: incubators, accelerators and startup programs tend to have agreements on credit packages with providers. Reach out to your program manager if you're into one of those!&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Observe your AI costs and take back control
&lt;/h2&gt;

&lt;p&gt;Last but not least, &lt;strong&gt;you can't optimize what you can't see&lt;/strong&gt;. Maybe one single recurrent LLM call is burning all your budget and you haven't identified it yet.&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%2F5uw3nm463s05f7c9x8p4.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%2F5uw3nm463s05f7c9x8p4.png" alt="30-day messages and usage chart from the Manifest dashboard — track your AI inference volume and cost over time" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many tools (open source or proprietary) like Manifest that let you visualize your costs, analyse them, and even set some budget limits. Some of them are simple and easy to understand, others more complicated and let you go into details. Up to you to find your fit!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reducing cloud AI inference costs is key for an app's profitability.&lt;/strong&gt; Consider the few lines of each point in this post as a starting point, an idea to dig into. Not all of them are necessarily applicable in your case, but they are worth knowing and understanding.&lt;/p&gt;

&lt;p&gt;At Manifest, we think that AI is an incredible technology, and that it deserves to be affordable. Our platform is open source and gives total control to our users. Check out &lt;a href="https://manifest.build/" rel="noopener noreferrer"&gt;our website&lt;/a&gt; and give us a &lt;a href="https://github.com/mnfst/manifest" rel="noopener noreferrer"&gt;star on GitHub&lt;/a&gt; to support us! Or simply share this post, as it helps others pay less for AI.&lt;/p&gt;

&lt;p&gt;Happy hacking!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>money</category>
      <category>models</category>
    </item>
    <item>
      <title>Save money on AI using those permanent free LLM APIs</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Sat, 28 Mar 2026 20:06:43 +0000</pubDate>
      <link>https://dev.to/bd_perez/save-money-on-ai-using-those-permanent-free-llm-apis-19ec</link>
      <guid>https://dev.to/bd_perez/save-money-on-ai-using-those-permanent-free-llm-apis-19ec</guid>
      <description>&lt;p&gt;Those LLM APIs offer permanent free tiers for text inference (no trial or initial credits, permanent tier only).&lt;/p&gt;

&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Provider APIs&lt;/li&gt;
&lt;li&gt;Inference providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Provider APIs
&lt;/h2&gt;

&lt;p&gt;APIs run by the companies that train or fine-tune the models themselves.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dashboard.cohere.com/api-keys" rel="noopener noreferrer"&gt;Cohere&lt;/a&gt; 🇺🇸 - Command A, Command R+, Aya Expanse 32B +9 more. 20 RPM, 1K/mo.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aistudio.google.com/app/apikey" rel="noopener noreferrer"&gt;Google Gemini&lt;/a&gt; 🇺🇸 - Gemini 2.5 Pro, Flash, Flash-Lite +4 more. 5-15 RPM, 100-1K RPD. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://console.mistral.ai/api-keys" rel="noopener noreferrer"&gt;Mistral AI&lt;/a&gt; 🇪🇺 - Mistral Large 3, Small 3.1, Ministral 8B +3 more. 1 req/s, 1B tok/mo.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://open.bigmodel.cn/usercenter/apikeys" rel="noopener noreferrer"&gt;Zhipu AI&lt;/a&gt; 🇨🇳 - GLM-4.7-Flash, GLM-4.5-Flash, GLM-4.6V-Flash. Limits undocumented.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Inference providers
&lt;/h2&gt;

&lt;p&gt;Third-party platforms that host open-weight models from various sources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.cerebras.ai/" rel="noopener noreferrer"&gt;Cerebras&lt;/a&gt; 🇺🇸 - Llama 3.3 70B, Qwen3 235B, GPT-OSS-120B +3 more. 30 RPM, 14,400 RPD.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dash.cloudflare.com/profile/api-tokens" rel="noopener noreferrer"&gt;Cloudflare Workers AI&lt;/a&gt; 🇺🇸 - Llama 3.3 70B, Qwen QwQ 32B +47 more. 10K neurons/day.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/marketplace/models" rel="noopener noreferrer"&gt;GitHub Models&lt;/a&gt; 🇺🇸 - GPT-4o, Llama 3.3 70B, DeepSeek-R1 +more. 10-15 RPM, 50-150 RPD.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://console.groq.com/keys" rel="noopener noreferrer"&gt;Groq&lt;/a&gt; 🇺🇸 - Llama 3.3 70B, Llama 4 Scout, Kimi K2 +17 more. 30 RPM, 1K RPD (14,400 for Llama 3.1 8B).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huggingface.co/settings/tokens" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; 🇺🇸 - Llama 3.3 70B, Qwen2.5 72B, Mistral 7B +many more. $0.10/mo in free credits.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.kluster.ai/apikeys" rel="noopener noreferrer"&gt;Kluster AI&lt;/a&gt; 🇺🇸 - DeepSeek-R1, Llama 4 Maverick, Qwen3-235B +2 more. Limits undocumented.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://token.llm7.io" rel="noopener noreferrer"&gt;LLM7.io&lt;/a&gt; 🇬🇧 - DeepSeek R1, Flash-Lite, Qwen2.5 Coder +27 more. 30 RPM (120 with token).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://build.nvidia.com/explore/discover" rel="noopener noreferrer"&gt;NVIDIA NIM&lt;/a&gt; 🇺🇸 - Llama 3.3 70B, Mistral Large, Qwen3 235B +more. 40 RPM.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/settings/keys" rel="noopener noreferrer"&gt;Ollama Cloud&lt;/a&gt; 🇺🇸 - DeepSeek-V3.2, Qwen3.5, Kimi-K2.5 +17 more. 1 concurrent model, light usage. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openrouter.ai/keys" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; 🇺🇸 - DeepSeek R1, Llama 3.3 70B, GPT-OSS-120B +29 more. 20 RPM, 50 RPD (1K with $10+ in purchased credits).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.siliconflow.cn/account/ak" rel="noopener noreferrer"&gt;SiliconFlow&lt;/a&gt; 🇨🇳 - Qwen3-8B, DeepSeek-R1-Distill-Qwen-7B, GLM-4.1V-9B-Thinking +10 more. 1K RPM, 50K TPM.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;This list changes fast. Star the &lt;a href="https://github.com/mnfst/awesome-free-llm-apis" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; to get notified when we add providers, and open a PR if you spot one we missed.&lt;/p&gt;

&lt;p&gt;Cheers!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>llm</category>
    </item>
    <item>
      <title>What Is An LLM Router?</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Sat, 21 Mar 2026 22:52:38 +0000</pubDate>
      <link>https://dev.to/bd_perez/what-is-an-llm-router-463d</link>
      <guid>https://dev.to/bd_perez/what-is-an-llm-router-463d</guid>
      <description>&lt;p&gt;An &lt;strong&gt;LLM Router&lt;/strong&gt; is a piece of software that directs prompts to different models. Instead of using always the same model for each request, the router redirects each query to a different model. LLM Routing is used mostly for 3 different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost saving&lt;/strong&gt;: Using a cheaper model when handling easy tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specialization&lt;/strong&gt;: Use specialist models when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability&lt;/strong&gt;: Using fallback models when one is down or load balancing &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;LLM Routers do the routing automatically so the user experience is smooth and without friction. They differ from LLM Gateways as those ones are more about managing the traffic, ensuring observability and governance rather than choosing the right model for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule-based routing vs AI-powered routing
&lt;/h2&gt;

&lt;p&gt;LLM Routers can be grouped into 2 different approaches: rule-based routers which are programmatic and AI-powered routers that use a model to do the redirect.&lt;/p&gt;

&lt;p&gt;Rule-based routers, like &lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt;, are the simplest to implement: they are fast, deterministic and can run everywhere (client or server). AI-powered routers on the other hand are more powerful and adapt to more use cases, but they cost inference and add extra latency to queries.&lt;/p&gt;

&lt;p&gt;Some hybrid approaches are quite effective but they will inherit all AI-powered routers cons (non-deterministic, cost, latency, infra) even if minimized.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM Routing for OpenClaw and autonomous agents
&lt;/h2&gt;

&lt;p&gt;By default, OpenClaw (and other autonomous agents) only enables you to connect to 1 model only. This model will handle all the requests from the simplest to the most demanding, from &lt;a href="https://docs.openclaw.ai/gateway/heartbeat" rel="noopener noreferrer"&gt;heartbeats&lt;/a&gt; to really complex tasks. It is hard for users to get to a winning trade-off: they either use a top-tier model resulting in extra costs or a cheap model reducing the quality of outputs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt; solves that problem by giving you access to 4 different tiers of complexity where you can add a different model for each. It is a very effective way of using routing to reduce costs and maximize the quality of your OpenClaw. It is open source and has both cloud and 100% local version.&lt;/p&gt;

&lt;p&gt;Manifest solves that problem by giving you access to 4 different tiers of complexity where you can add a different model for each. It is a very effective way of using routing to reduce costs and maximize the quality of your OpenClaw. It is open source and has both cloud and 100% local version.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Hyper Specialization: Stockfish, Adam Smith and Saving Our Jobs in the AI Era</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Wed, 11 Mar 2026 19:10:43 +0000</pubDate>
      <link>https://dev.to/bd_perez/hyper-specialization-stockfish-adam-smith-and-saving-our-jobs-in-the-ai-era-1jle</link>
      <guid>https://dev.to/bd_perez/hyper-specialization-stockfish-adam-smith-and-saving-our-jobs-in-the-ai-era-1jle</guid>
      <description>&lt;p&gt;It's a mistake to think we'll stay relevant by orchestrating AIs. Our salvation may be the opposite: hyper specialization.&lt;/p&gt;

&lt;p&gt;Like many software engineers today, I haven’t written any meaningful code myself for months but I am still the one in charge: I choose the tech stack, approach and structure and the coding agents execute.&lt;/p&gt;

&lt;p&gt;But agents are quickly reaching the orchestration layer. They ask less, make assumptions and take decisions, mostly the correct ones. It’s just a matter of time before I simply describe my problem and the AI figures out what to build and how.&lt;/p&gt;

&lt;p&gt;But even if an absurdly powerful AI can do everything itself, it doesn’t mean it should. Being a generalist has a cost. Using an overqualified AI to do a simple task is like using a sledgehammer to crack a nut. In most cases, contracting a specialist system for that purpose is not just cheaper, but also better.&lt;/p&gt;

&lt;p&gt;Adam Smith wrote in 1776 that “divide work into narrow tasks and let focused workers do each one better and faster”. It still makes sense today, and more importantly, tomorrow.&lt;/p&gt;

&lt;p&gt;Not convinced? Let me tell you about Stockfish. Stockfish is a chess bot, the best one. Really powerful at chess but nothing else, not something we would call “AI”, whatever that means. It actually can run on any outdated phone. Now if we play a million chess games, Stockfish on a phone versus the latest frontier models on cutting-edge data centers, how many times do the models win?&lt;/p&gt;

&lt;p&gt;Zero.&lt;/p&gt;

&lt;p&gt;Not once. No wins. No draws. Not even close, the gap is abysmal. Adam Smith was right from the beginning: specialists outperform generalists in all ways. Even in the AI era.&lt;/p&gt;

&lt;p&gt;Now if a generalist contracts a specialist to execute a task, we still have to consider the cost of delegation. It’s often easier to do it yourself, as delegation has friction. It comes in two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The finding cost:&lt;/strong&gt; Discover the service and evaluate it before hiring it. Can I trust it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Connecting cost:&lt;/strong&gt; Integrate the service to our system. Does it plug in easily? How do we pay?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The math is simple:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cost of execution (diy) ≶ Cost of delegation + Cost of the task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Note: Two factors don’t show up in this equation: risk and governance. A powerful system may prefer to handle sensitive tasks itself to keep control, even at a worse price. Classic make vs buy.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
In a world where powerful AI systems will be able to do most of our jobs, there is still a path for individuals and organizations to deliver economic value. That value would not be in controlling those systems but rather in becoming the specialist that AI systems need to contract.&lt;/p&gt;

&lt;p&gt;If you build a service that fills tax forms perfectly, regardless of what’s inside the box: AI, software, or humans, you will be better at that one thing and therefore become profitable as each additional customer has a low marginal cost.&lt;/p&gt;

&lt;p&gt;Our goal at &lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt; is to lower the delegation cost as close to zero as possible. This means building infrastructure where AIs autonomously discover, evaluate, and use specialized services. Connection and payment should be instant too. Reducing delegation friction is key to enable this new economy.&lt;/p&gt;

&lt;p&gt;Don’t orchestrate AI, beat it at one thing.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How To Shut Up OpenClaw CLI Banner 🦞</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Sat, 28 Feb 2026 00:10:51 +0000</pubDate>
      <link>https://dev.to/bd_perez/how-to-shut-up-openclaw-cli-banner-3g1j</link>
      <guid>https://dev.to/bd_perez/how-to-shut-up-openclaw-cli-banner-3g1j</guid>
      <description>&lt;p&gt;Every time you run an OpenClaw command, you’re greeted with a little lobster and a “funny” tagline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🦞 OpenClaw 2026.2.15 (3fe22ea)
Gateway online—please keep hands, feet, and appendages inside the shell at all times.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cute the first time but by the hundredth invocation you start wondering if the lobster is mocking you personally. Moreover, it becomes hard to distinguish the outputs from your command with those vertical spaces.&lt;/p&gt;

&lt;p&gt;I went down the plugin rabbit hole trying to suppress it — turns out the banner fires during CLI bootstrap, before the plugin system even loads. So no, you can’t write a plugin to kill it. The lobster is faster than you.&lt;/p&gt;

&lt;p&gt;But buried in the source code there’s a single env var doing the lord’s work:&lt;/p&gt;

&lt;p&gt;Add to your ~/.zshrc (or ~/.bashrc):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export OPENCLAW_HIDE_BANNER=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. One line. source ~/.zshrc and the lobster goes silent.&lt;/p&gt;

&lt;p&gt;You’re welcome.&lt;/p&gt;

&lt;p&gt;And if you're an stingy OpenClaw user, check out &lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt; and stop paying too much tokens by routing queries to the right LLM.  &lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>cli</category>
      <category>jokes</category>
    </item>
    <item>
      <title>User flows are the new apps</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Mon, 12 Jan 2026 17:04:49 +0000</pubDate>
      <link>https://dev.to/bd_perez/user-flows-are-the-new-apps-2cpj</link>
      <guid>https://dev.to/bd_perez/user-flows-are-the-new-apps-2cpj</guid>
      <description>&lt;p&gt;&lt;strong&gt;ChatGPT Apps open a new way of considering apps, defining them by a set of flows triggered by an intent detection, rather than by a whole entity. For the user's benefit.&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fs34ktd5xsil2tejh0qnh.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.amazonaws.com%2Fuploads%2Farticles%2Fs34ktd5xsil2tejh0qnh.jpg" alt="flows everywhere" width="735" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How traditional apps work
&lt;/h2&gt;

&lt;p&gt;If we think about it, the way we ship apps is extremely inefficient: we send the integrity of what we offer to people who will only use a tiny fraction of it. This is literally what happens when you download a mobile app for example, or when you load a web app on your browser. Of course, you can answer saying "&lt;em&gt;Who cares ? Storage and bandwidth are cheap!&lt;/em&gt;" and you'll be right to think so.&lt;/p&gt;

&lt;p&gt;However, there is more than potential performance issues when delivering the whole jungle to someone who asked for a banana: we are over-complicating our apps, forcing users to figure out themselves where they should go. Complexity takes real estate and makes it overwhelming for users, increasing the time and energy required to complete an action.&lt;/p&gt;

&lt;p&gt;I know we do consider &lt;em&gt;user stories&lt;/em&gt; and improve them, but when the same platform delivers hundreds of them, they cannot be intuitive. Let's take an example: I have many bank accounts and something I often do is to display my bank account details. Simple, right? But in all my apps that feature is NEVER at the same place, so it pisses me off.&lt;/p&gt;

&lt;p&gt;This could have been done better right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Considering User flows
&lt;/h2&gt;

&lt;p&gt;A user flow is the path of a user who completes a task. It starts with an intent and ends up with a goal, with some steps in the middle. It is a bit different than workflows, another sequence of steps that is centered on processes, not users.&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%2F03disxr456ymlbul3qzr.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%2F03disxr456ymlbul3qzr.png" alt="n8n backend workflow vision" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In both cases, we describe our app not by what it is or how it's built, but what it does. Even if we make some technical shortcuts, we are caring more about our users than ourselves, good direction!&lt;/p&gt;

&lt;p&gt;Right, but nothing new here! Correct. But now we have at our disposal the perfect tool to detect user's intent: AI.&lt;/p&gt;

&lt;p&gt;And this changes literately everything.&lt;/p&gt;

&lt;p&gt;Instantly understanding user's intention and delivering the corresponding flow makes everything better for the user.&lt;/p&gt;

&lt;p&gt;Let's go back to my bank account details. Imagine the prompt on a chat like ChatGPT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@myBank display my bank account details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sentence says it all! There is 0 ambiguity about my intent even for the dumbest LLM. The app can now show me my bank account in the simplest way, because the system knows that I need that and nothing else.&lt;/p&gt;

&lt;p&gt;And this is where the magic happens: &lt;strong&gt;we can now safely remove the 99% of the app&lt;/strong&gt; and only deliver the content that our users want.&lt;/p&gt;

&lt;p&gt;We can rethink our banking app segmented by user flows:&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%2Fp9wkj5yd9l9y7os6mbfr.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%2Fp9wkj5yd9l9y7os6mbfr.png" alt="user flows" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That simplified example can be powered by a chat agent and an MCP Server, with an optional &lt;a href="https://ui.manifest.build" rel="noopener noreferrer"&gt;in-chat UI&lt;/a&gt;. If we get creative, we can imagine similar user flows with any AI system capable of guessing an intent and trigger a flow, either visual, vocal or physical&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Are flows going to replace apps?
&lt;/h2&gt;

&lt;p&gt;Not really. We still need apps and websites for users to browse content, forge their opinion on something, dig on something and so on. Browsing is serendipity, it's nice.&lt;/p&gt;

&lt;p&gt;However, sometimes users have really straightforward intentions, and this is where flows are effective. Delivering only what people want is way faster and easier for users.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>ux</category>
      <category>agents</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I put a Game Boy inside ChatGPT (ChatGPT Apps)</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Fri, 19 Dec 2025 01:01:44 +0000</pubDate>
      <link>https://dev.to/bd_perez/i-put-a-game-boy-inside-chatgpt-chatgpt-apps-3a75</link>
      <guid>https://dev.to/bd_perez/i-put-a-game-boy-inside-chatgpt-chatgpt-apps-3a75</guid>
      <description>&lt;p&gt;&lt;strong&gt;ChatGPT Apps&lt;/strong&gt; are still in BETA but since yesterday, every developer can submit their apps to their registry.&lt;/p&gt;

&lt;p&gt;I decided to try to fit a Game Boy inside Chat GPT to see if it worked... and it did !&lt;/p&gt;

&lt;p&gt;See some screenshots below:&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%2Fj4lfnmxlix7prac0hr1r.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%2Fj4lfnmxlix7prac0hr1r.png" alt="Zelda Game Boy Emulator inside ChatGPT" width="800" height="612"&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.amazonaws.com%2Fuploads%2Farticles%2F7kkum56m48hxcaheze9u.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%2F7kkum56m48hxcaheze9u.png" alt="Tetris inside ChatGPT" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to play Game Boy inside ChatGPT ?
&lt;/h2&gt;

&lt;p&gt;For now you need to activate the &lt;strong&gt;developer mode&lt;/strong&gt; in (settings &amp;gt; apps &amp;gt; advanced settings) and create a new app called "GameBoy" and paste this URL &lt;code&gt;https://gameboy.manifest.build/mcp&lt;/code&gt; as &lt;strong&gt;MCP Server URL&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Grab your favorite ROM (obviously you need to own the original copy of it), upload it to the chat and mention @GameBoy to launch the game.&lt;/p&gt;

&lt;p&gt;This project is &lt;a href="https://github.com/brunobuddy/gameboy-emulator-chatgpt-app" rel="noopener noreferrer"&gt;open source&lt;/a&gt; and based on 2 other great open source projects: &lt;a href="https://github.com/juchi/gameboy.js/" rel="noopener noreferrer"&gt;Gameboy.js&lt;/a&gt; and &lt;a href="https://github.com/attackemartin/css-gameboy" rel="noopener noreferrer"&gt;CSS Gameboy&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have fun!&lt;/p&gt;

</description>
      <category>chatgptapps</category>
      <category>chatgpt</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Create Your First MCP App</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Fri, 12 Dec 2025 23:05:36 +0000</pubDate>
      <link>https://dev.to/bd_perez/create-your-first-mcp-app-2c65</link>
      <guid>https://dev.to/bd_perez/create-your-first-mcp-app-2c65</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR;&lt;/strong&gt; MCP Apps are bringing interactive UIs to conversational agents and other MCP clients. While this official extension is still a but can bring new ways to interact with apps and content.&lt;/p&gt;

&lt;p&gt;In this tutorial we will show how to create a simple yet powerful app (&lt;a href="https://github.com/brunobuddy/first-mcp-app" rel="noopener noreferrer"&gt;source code here&lt;/a&gt;) that can serve as a template for bigger projects. We will create an Express server (Node.js) using TypeScript, Vite and the 2 official MCP SDKs (&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;TS server SDK&lt;/a&gt; and &lt;a href="https://github.com/modelcontextprotocol/ext-apps" rel="noopener noreferrer"&gt;ext apps&lt;/a&gt;. The app will show the last flights that arrived to an airport in a nice way.&lt;/p&gt;

&lt;p&gt;We will do this in 3 steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an MCP server&lt;/li&gt;
&lt;li&gt;Register a tool&lt;/li&gt;
&lt;li&gt;Register a resource and connect to tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create an MCP server
&lt;/h2&gt;

&lt;p&gt;We are going to the TypeScript SDK to generate a simple MCP Server using the Streamable HTTP transport (recommended way).&lt;/p&gt;

&lt;p&gt;Let's start with the basics:&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="c"&gt;# Init project&lt;/span&gt;
npm init &lt;span class="nt"&gt;--y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm pkg &lt;span class="nb"&gt;set type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;
tsc &lt;span class="nt"&gt;--init&lt;/span&gt;

&lt;span class="c"&gt;# Create gitignore file&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"node_modules&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;dist"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .gitignore

&lt;span class="c"&gt;# Install deps&lt;/span&gt;
npm i @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps express zod &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm i &lt;span class="nt"&gt;-D&lt;/span&gt; @types/express nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our core logic will be included in our &lt;code&gt;server.ts&lt;/code&gt; file:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&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="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StreamableHTTPServerTransport&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="s2"&gt;@modelcontextprotocol/sdk/server/streamableHttp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerGetFlightsTool&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="s2"&gt;./tools/get-flights.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerFlightCardResource&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="s2"&gt;./resources/flight-card/flight-card.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;server&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;McpServer&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="s2"&gt;My First MCP App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&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;// Set up Express server to handle MCP requests.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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="nf"&gt;use&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="nf"&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="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mcp&lt;/span&gt;&lt;span class="dl"&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;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="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;transport&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;StreamableHTTPServerTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;sessionIdGenerator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enableJsonResponse&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&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;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRequest&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;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="k"&gt;catch&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="c1"&gt;// Start the server.&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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="nf"&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;MCP server listening on http://localhost:3000/mcp&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;p&gt;Then we want to create a pleasant development environment that serves the MCP server and reloads when we change a file. We are going to use nodemon for this. Create a &lt;code&gt;nodemon.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ts,html,css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc &amp;amp;&amp;amp; node dist/server.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And adapt the &lt;code&gt;package.json&lt;/code&gt; to add a new script&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"dev": "nodemon"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last but not least, we have to make sure that the complied files goes to the &lt;code&gt;dist&lt;/code&gt; folder. In the &lt;code&gt;tsconfig.json&lt;/code&gt;, uncomment the &lt;code&gt;"outDir": "./dist",&lt;/code&gt; moment.&lt;/p&gt;

&lt;p&gt;Here we go! We can now run our server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are going to use MCP Jam to test that our MCP server is running correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @mcpjam/inspector@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add a server with URL &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt;. The connection should be successful:&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%2F29sona46b5m11we16h9h.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%2F29sona46b5m11we16h9h.png" alt="MCP Demo server running on port 3000" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bravo! Take a sip of coffee because you just finished the step 1 of this tutorial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register a tool to your MCP server
&lt;/h2&gt;

&lt;p&gt;Our server is feeling pretty lonely without any tool or resource around! Let's create our first tool that is going to fetch the last flights from an airport.&lt;/p&gt;

&lt;p&gt;We are going to return the next flights arriving at a specific airport. Our tool will receive the airport code as input and return an array of flight arrivals. For now we are mocking the flights response, but the point of those MCP tools is to connect to real-world APIs here.&lt;/p&gt;

&lt;p&gt;We create a &lt;code&gt;tools/get-flights.ts&lt;/code&gt; and register it in the main &lt;code&gt;server.ts&lt;/code&gt; file.&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="c1"&gt;// tools/get-flights.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&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="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;flightCardResourceUri&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="s2"&gt;../resources/flight-card/flight-card.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerGetFlightsTool&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;McpServer&lt;/span&gt;&lt;span class="p"&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="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-flights&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retrieves flight arrivals for a given airport code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The ICAO airport code, e.g. 'KJFK'&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;// Mock flight data for demonstration purposes. In a real implementation, you would fetch&lt;/span&gt;
      &lt;span class="c1"&gt;// this data from an external API.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockFlights&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;flightNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AA100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;American Airlines&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;flightNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DL200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Delta Airlines&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;flightNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UA300&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;United Airlines&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;content&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="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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="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;mockFlights&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="na"&gt;structuredContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockFlights&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And register the tool in your &lt;code&gt;server.ts&lt;/code&gt; file:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerGetFlightsTool&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="s2"&gt;./tools/get-flights.js&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;McpServer&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="s2"&gt;My First MCP App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&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;// Register the tool.&lt;/span&gt;
&lt;span class="nf"&gt;registerGetFlightsTool&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our tool is now live and can be called dynamically in our chat playground. Cheers! You just end up the second part of this tutorial!&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%2F2es6jz27694c6mk4q9ur.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%2F2es6jz27694c6mk4q9ur.png" alt="MCP tool in chat screenshot" width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Register a UI resource and link it to our tool
&lt;/h2&gt;

&lt;p&gt;Now we want to render the tool result in a UI within our chat application. Let's install more deps&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i vite vite-plugin-singlefile glob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now we just compiled TypeScript code into JavaScript but now we will use Vite to convert HTML too. We also want to use &lt;code&gt;vite-singlefile&lt;/code&gt; plugin to gather the logic in the same file to return the whole MCP App in one request.&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="c1"&gt;// vite.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;viteSingleFile&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="s2"&gt;vite-plugin-singlefile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;viteSingleFile&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;emptyOutDir&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;rollupOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;input&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;flight-card&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;resources/flight-card/mcp-app/flight-card-mcp-app.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Add more HTML resources here as needed&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;Adapt the &lt;code&gt;nodemon.json&lt;/code&gt; exec script adding the &lt;code&gt;vite build&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"exec": "vite build &amp;amp;&amp;amp; tsc &amp;amp;&amp;amp; node dist/server.js"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And let's declare the flight card resource:&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;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&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="s2"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:path&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 a unique URI for the flight card resource.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flightCardResourceUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui://flight-card.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerFlightCardResource&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;McpServer&lt;/span&gt;&lt;span class="p"&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="nf"&gt;registerResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;flightCardResourceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;flightCardResourceUri&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Read the HTML content from the file system.&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mcp-app/flight-card-mcp-app.html&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;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&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="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;flightCardResourceUri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html;profile=mcp-app&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;html&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="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;And register the resource in your &lt;code&gt;server.ts&lt;/code&gt; file:&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="c1"&gt;// server.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerGetFlightsTool&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="s2"&gt;./tools/get-flights.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerFlightCardResource&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="s2"&gt;./resources/flight-card/flight-card.js&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;McpServer&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="s2"&gt;My First MCP App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&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;// Register tools and resources.&lt;/span&gt;
&lt;span class="nf"&gt;registerGetFlightsTool&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="nf"&gt;registerFlightCardResource&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can create the flight card MCP app, which will be an HTML template and a TS module. I created a new folder at &lt;code&gt;/tools/flight-card/mcp-app&lt;/code&gt; to put those 2 files.&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="c"&gt;&amp;lt;!-- tools/flight-card/mcp-app/flight-card-mcp-app.html --&amp;gt;&lt;/span&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;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"./flight-card-mcp-app.ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Flight Cards&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;*&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="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-apple-system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BlinkMacSystemFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Segoe UI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Roboto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nb"&gt;sans-serif&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="m"&gt;#f0f2f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;280px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&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="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.card&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="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.icon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;48px&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="m"&gt;48px&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="m"&gt;#e8f4fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.info&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.flight-number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;18px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a2e&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nc"&gt;.airline&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#666&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"flights"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&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;And the TS module:&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="c1"&gt;// tools/flight-card/mcp-app/flight-card-mcp-app.ts&lt;/span&gt;
&lt;span class="k"&gt;import&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;PostMessageTransport&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="s2"&gt;@modelcontextprotocol/ext-apps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create a new MCP App instance.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&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;App&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="s2"&gt;Flight Card MCP App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.1&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flightsEl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flights&lt;/span&gt;&lt;span class="dl"&gt;"&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="c1"&gt;// Handle tool results to display flight information.&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;ontoolresult&lt;/span&gt; &lt;span class="o"&gt;=&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="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;flights&lt;/span&gt; &lt;span class="o"&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;structuredContent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;flights&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flightNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}[];&lt;/span&gt;

  &lt;span class="nx"&gt;flights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;flight&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;flightsEl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`
        &amp;lt;div class="card"&amp;gt;
          &amp;lt;div class="icon"&amp;gt;✈️&amp;lt;/div&amp;gt;
          &amp;lt;div class="info"&amp;gt;
            &amp;lt;div class="flight-number"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flightNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class="airline"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;flight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;airline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;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="nf"&gt;connect&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;PostMessageTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And last but not least, go back to your &lt;code&gt;get-flights&lt;/code&gt; tool and add link to your resource using the &lt;code&gt;_meta&lt;/code&gt; key.&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="c1"&gt;// tools/get-flights.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RESOURCE_URI_META_KEY&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="s2"&gt;@modelcontextprotocol/ext-apps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;flightCardResourceUri&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="s2"&gt;../resources/flight-card/flight-card.js&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;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get-flights&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retrieves flight arrivals for a given airport code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The ICAO airport code, e.g. 'KJFK'&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;// Link to resource here.&lt;/span&gt;
      &lt;span class="na"&gt;_meta&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;RESOURCE_URI_META_KEY&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;flightCardResourceUri&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;Bootsrapping all together, we are now able to see the UI appear when you ask the flights! Checkout the &lt;a href="https://github.com/brunobuddy/first-mcp-app" rel="noopener noreferrer"&gt;source GitHub repo&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.amazonaws.com%2Fuploads%2Farticles%2F6yunmb7l5poji4dl2wyx.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%2F6yunmb7l5poji4dl2wyx.png" alt="MCP app in chat" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal thoughts
&lt;/h2&gt;

&lt;p&gt;The MCP App extension is still a draft but we can already see where this is heading next: more focused and "intent-based" interfaces that deliver minimalistic yet effective experiences. &lt;/p&gt;

&lt;p&gt;Of course, the browser experience is not going anywhere soon but we sense that many use cases can benefit from being ported to the chat format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI-intensive user stories that could be accelerated through this format (like long multi-step forms)&lt;/li&gt;
&lt;li&gt;Brands that want to deliver rich experiences to tell a narrative despite website traffic loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the technical side we hope to see soon more MCP clients supporting this protocol.&lt;/p&gt;

&lt;p&gt;What use cases are you seeing for this extension? Are you seeing some drawbacks or challenges? Leave your take on the comments.&lt;/p&gt;

</description>
      <category>mcpapp</category>
      <category>mcp</category>
      <category>llm</category>
    </item>
    <item>
      <title>Supabase alternative for AI Code editors (Cursor, Bolt, Lovable...)</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Thu, 19 Jun 2025 13:26:26 +0000</pubDate>
      <link>https://dev.to/bd_perez/supabase-alternative-for-ai-code-editors-cursor-bolt-lovable-2d16</link>
      <guid>https://dev.to/bd_perez/supabase-alternative-for-ai-code-editors-cursor-bolt-lovable-2d16</guid>
      <description>&lt;p&gt;In this post I compare existing solutions for &lt;strong&gt;the backend of AI generated apps&lt;/strong&gt; and share our journey building &lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt;, a minimalistic open source backend for AI-assisted coding. &lt;/p&gt;

&lt;p&gt;As of today, AI code editors already work very smoothly with frontend generation. Even a non coder is able to create nice UIs and websites with a bit of motivation. However vibe coding the backend side is still a bit laborious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bootstrap approach to backend (local tools)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Local AI code editors&lt;/strong&gt; like &lt;a href="https://www.cursor.com/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;, &lt;a href="http://windsurf.com/" rel="noopener noreferrer"&gt;Windsurf&lt;/a&gt; and &lt;a href="https://code.visualstudio.com/docs/copilot/overview" rel="noopener noreferrer"&gt;VS Code with Copilot&lt;/a&gt; are made for people already familiar with IDEs, matching high requirements like a strong version control or working with existing/legacy codebases.&lt;/p&gt;

&lt;p&gt;If you ask any of those editor to create a backend, chances are that you will get something made with FastAPI or Django in Python, Laravel in PHP, Ruby on rails, NestJS and so on. &lt;/p&gt;

&lt;p&gt;All of those frameworks have proven to be exceptional products, robust and highly customizable. They have been used by developers for years support top tier platforms.&lt;/p&gt;

&lt;p&gt;However, in the context of AI-assisted programming, they have some drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backend with those frameworks consists necessarily in a huge amount of files which makes it nearly impossible to be humanly validated and cost a lot in computing (tokens)&lt;/li&gt;
&lt;li&gt;Each feature is mostly developed in the spot for you and may contain security flaws for unknown reasons&lt;/li&gt;
&lt;li&gt;The developer must still have solid knowledge with the framework to understand it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Supabase approach to backend (online tools)
&lt;/h2&gt;

&lt;p&gt;Then you have the &lt;strong&gt;online vibe coding tools&lt;/strong&gt; like &lt;a href="https://bolt.new/" rel="noopener noreferrer"&gt;Bolt.new&lt;/a&gt;, &lt;a href="https://lovable.dev/" rel="noopener noreferrer"&gt;Lovable&lt;/a&gt;, &lt;a href="https://www.co.dev/" rel="noopener noreferrer"&gt;co.dev&lt;/a&gt; and so on. Those tools are addressing a large audience and require little knowledge to get started. They are amazing to quickly ship a project from scratch. &lt;/p&gt;

&lt;p&gt;What do they have in common regarding the backend ? They all have the "connect to Supabase" button. It actually makes a lot of sense as it is so much simpler to rely on a cloud backend rather than creating a consistent environment for different backend stacks. &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%2Fji691586igy91tljifec.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%2Fji691586igy91tljifec.png" alt=" " width="497" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; is a well-know BaaS (Backend as a Service) based on PostgreSQL and giving built-in features like auth or storage. Nevertheless, even if Supabase is an amazing product on its own, linking your AI-generated frontend to a Supabase backend can be tricky for many reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having to deal with 2 projects creates a poor DX, making you going back and forth between 2 dashboards and the LLM not always understanding what is on the other side&lt;/li&gt;
&lt;li&gt;Branching, versionning and environment management becomes suddenly way more complex&lt;/li&gt;
&lt;li&gt;You may not be familiar with PostgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if Supabase is building a strong MCP to connect the service, it will always be 2 connected services at the end of the day. &lt;/p&gt;

&lt;h2&gt;
  
  
  Manifest: finding the sweet spot
&lt;/h2&gt;

&lt;p&gt;When we first built &lt;a href="https://github.com/mnfst/manifest" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt;, as an internal tool, we did not think right away about making it fit in the context of AI coding. However, we strongly focused on simplicity of use and reducing the development time. Those characteristics are now 100x enhanced by AI code tools that we use today which makes a perfect fit for Manifest as a backend for AI editors.&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%2Fkz4kyuqsqmdkb7yh501b.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%2Fkz4kyuqsqmdkb7yh501b.png" alt=" " width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Manifest is a complete backend that fits into 1 simple YAML file, that makes it so easy for LLMs to generate that content, and for humans to validate. Relying on pre-made components like auth or upload modules is also a way of ensuring the developer that there is no breaches in security or privacy. &lt;/p&gt;

&lt;p&gt;We also wanted Manifest to be not only open source but also 100% portable, it's just an &lt;a href="//npmjs.com/package/manifest"&gt;npm package&lt;/a&gt; that you can add to any codebase with a &lt;code&gt;package.json&lt;/code&gt; in it. Manifest uses SQLite by default, a file-based DB that works even in browser environments like &lt;a href="https://manifest.new/" rel="noopener noreferrer"&gt;Stackblitz&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we go next
&lt;/h2&gt;

&lt;p&gt;Manifest is still on BETA but already used on multiple projects. If you think Manifest could be a good fit for you, give it a try ! You can share some love by &lt;a href="https://github.com/mnfst/manifest" rel="noopener noreferrer"&gt;giving Manifest a star on GitHub&lt;/a&gt; and we'll be happy to hear your feedback on our &lt;a href="https://discord.gg/FepAked3W7" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>ai</category>
      <category>backend</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>From DEV to CTO: Embracing technical Debt</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Wed, 04 Dec 2024 09:22:58 +0000</pubDate>
      <link>https://dev.to/bd_perez/from-dev-to-cto-embracing-technical-debt-nl5</link>
      <guid>https://dev.to/bd_perez/from-dev-to-cto-embracing-technical-debt-nl5</guid>
      <description>&lt;h2&gt;
  
  
  How can I have a debt if I didn't borrow any money ?
&lt;/h2&gt;

&lt;p&gt;This is the first question that technical debt brings to mind. &lt;/p&gt;

&lt;p&gt;But let's approach technical dept with an example: Bob had to paint his flat, he spent all Saturday doing it. He was invited to &lt;strong&gt;a really cool party&lt;/strong&gt; on the evening. By nightfall, Bob had just finished painting but still needed to clean the brushes and put things away. He said "you know what ? f*** it", stuffed roughly all the paint pots and brushes in the closet and went to that party.&lt;/p&gt;

&lt;p&gt;What do you think about Bob's attitude ? Is he a pragmatic man ? Or a low-life and immature person ?&lt;/p&gt;

&lt;p&gt;There are 2 key things to understand in this story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bob could not have been to that party if he had to clean and put tools away after painting&lt;/li&gt;
&lt;li&gt;Bob ca no longer use his closet normally because his clothes are blocked by paint pots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding Bob's story is &lt;strong&gt;understanding technical debt&lt;/strong&gt; - there is nothing more to it. The next paragraphs are about helping you choose when to clean you brushes and when to go to the party. &lt;/p&gt;

&lt;h2&gt;
  
  
  Doing the right thing: a practical example
&lt;/h2&gt;

&lt;p&gt;Let's take a technical example: You have a component that needs to fetch data from an API. As a good developer, you know that the right thing to do is to create a service/store/hook/function (or whatever your framework calls it) to put all the API logic and inject it in your component.&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%2Fyr6jwssnjl3jcs56otjv.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.amazonaws.com%2Fuploads%2Farticles%2Fyr6jwssnjl3jcs56otjv.jpg" alt="creating technical debt" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;software development is about delivering things that work&lt;/strong&gt;. And the fastest way to do it is to skip the service thing and make that HTTP call directly from your component.&lt;/p&gt;

&lt;p&gt;Is it bad code ? Yes. Does it work ? Yes.&lt;/p&gt;

&lt;p&gt;There are obvious reasons why you should not do that: poor readability, it's hard to maintain, there is no separation of concerns and this will inevitably lead to WET code if that call is repeated elsewhere.&lt;/p&gt;

&lt;p&gt;However. &lt;/p&gt;

&lt;p&gt;If that code will never be touched again in the future (not likely) or, if you do not care about how this code will be changed in the future (more likely), &lt;strong&gt;bad code can be your best option&lt;/strong&gt;. End of chapter 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time capsule: doing it again
&lt;/h2&gt;

&lt;p&gt;Let's imagine that the you from the future opens that stinky codebase from chapter 1. You have to create a new component that calls the same API. See where we are going ? &lt;/p&gt;

&lt;p&gt;Once again we have two choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear that technical debt once for all and to that damn service, and implement it in both components &lt;/li&gt;
&lt;li&gt;increase the technical debt by ruthlessly copy-pasting your code in the new component&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%2Fkpmw8pslez2308tw78tk.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.amazonaws.com%2Fuploads%2Farticles%2Fkpmw8pslez2308tw78tk.jpg" alt="DRY code vs WET code with multiple components" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Does the trade-off change ? No. It is exactly the same situation as above, you have to choose based on the situation. Be quick or be solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  The outcome: mastering technical debt
&lt;/h2&gt;

&lt;p&gt;From a developers' perspective, it is clear: bad code is bad code, it is evil. Every book and tutorial will tell you exactly that you should never write bad code.&lt;/p&gt;

&lt;p&gt;Yet, from a different perspective, the reality is not that simple. Bad code is never 100% bad and neither is good code 100% good. There are also deadlines to respect, profitability to ensure, new contributors to integrate among many other factors. &lt;/p&gt;

&lt;p&gt;Does it mean that you have to make concessions and give up on good practices ? Not at all !  Bad code will definitely come back at you soon or late and you will pay it slowing development, weakening your app and annoy developers because no one likes to work on a stinky codebase. &lt;/p&gt;

&lt;p&gt;The point is finding the sweet spot: nothing is absolute, everything varies from situations. Sometimes you have to deliver quickly, sometimes you need to consolidate.&lt;/p&gt;

&lt;p&gt;It is when you start evaluating and embracing the technical debt, knowing what you get in exchange for it that you pass from developer to CTO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship fast without trading off your standards with Manifest
&lt;/h2&gt;

&lt;p&gt;If you understand that mastering technical debt is key, you will love &lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt; ! It is an open source 1-file backend that allows you to create complete backends quickly while keeping your coding conventions and standards. &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%2Fkeeft2hrkc6gmm8kjhgh.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%2Fkeeft2hrkc6gmm8kjhgh.png" alt="Manifest backend screenshot" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Star the &lt;a href="https://github.com/mnfst/manifest" rel="noopener noreferrer"&gt;Manifest project&lt;/a&gt; if you want to see Manifest grow ❤️.&lt;/p&gt;

</description>
      <category>development</category>
      <category>learning</category>
      <category>cto</category>
      <category>carrer</category>
    </item>
    <item>
      <title>A minimalist newsletter signup app with HTMX and Manifest</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Tue, 22 Oct 2024 12:46:48 +0000</pubDate>
      <link>https://dev.to/bd_perez/a-minimalist-newsletter-signup-app-with-htmx-and-manifest-4lg6</link>
      <guid>https://dev.to/bd_perez/a-minimalist-newsletter-signup-app-with-htmx-and-manifest-4lg6</guid>
      <description>&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;This simple full stack newsletter signup app has been built with minimal code. See the &lt;a href="https://github.com/brunobuddy/demo-htmx-manifest" rel="noopener noreferrer"&gt;source code&lt;/a&gt; or open the &lt;a href="https://stackblitz.com/fork/github/brunobuddy/demo-htmx-manifest" rel="noopener noreferrer"&gt;live stackblitz demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is HTMX ?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt; is an open source JS library that extends HTML allowing it to make HTTP requests (other than the default form POST) and update the DOM.&lt;/p&gt;

&lt;p&gt;Unlike JS frameworks like React, HTMX does not take control of your app. It is a minimalist library that integrates directly into HTML files. It works really well on small projects where you need to interact with a server.&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;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org@2.0.3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- have a button POST a click via AJAX --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/clicked"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Click Me
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What is Manifest ?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://manifest.build" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt; is an open source Backend-as-a-Service in 1 file that provides key backend features out-of-the-box. Manifest's purpose is to empower front-end developers enabling them to create backends easily and quickly. &lt;/p&gt;

&lt;p&gt;The syntax is a clear YAML file that generates an admin panel, a database and a fully functional REST API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# manifest/backend.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Healthcare application 🏥&lt;/span&gt;

&lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Doctor 👩🏾‍⚕️&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fullName&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;avatar&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;price&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;money&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;EUR&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;belongsTo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;City&lt;/span&gt;

  &lt;span class="na"&gt;Patient 🤒&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;fullName&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;birthdate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;date&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;belongsTo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Doctor&lt;/span&gt;

  &lt;span class="na"&gt;City 🌍&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why both ?
&lt;/h3&gt;

&lt;p&gt;In order to shine, HTMX needs to interact with a backend to fetch and post data. Instead of choosing a large backend framework that constrasts with the "micro-frontend" HTMX approach, Manifest fits better as it also provides a minimal and functional syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  The  mission
&lt;/h2&gt;

&lt;p&gt;In this demo, we will create a simple newsletter signup form with an email and a name input. The newly created subscribers get saved in the persistent database using Manifest and can be managed from the non-technical admin panel.&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%2Fw9fs85k8j6kpoxuir7bd.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%2Fw9fs85k8j6kpoxuir7bd.png" alt="screenshot form" width="687" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The HTMX
&lt;/h3&gt;

&lt;p&gt;Our HTMX consists in a small HTML form where we add 3 attributes: &lt;a href="https://htmx.org/attributes/hx-post/" rel="noopener noreferrer"&gt;hx-post&lt;/a&gt;, &lt;a href="https://htmx.org/attributes/hx-target/" rel="noopener noreferrer"&gt;hx-target&lt;/a&gt; and &lt;a href="https://htmx.org/attributes/hx-swap/" rel="noopener noreferrer"&gt;hx-swap&lt;/a&gt;.&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;h1&amp;gt;&lt;/span&gt;Subscribe to our Newsletter&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- We use hx-post to make an HTTP POST request on submit --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
      &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:1111/api/dynamic/subscribers"&lt;/span&gt;
      &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#message"&lt;/span&gt;
      &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHTML"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- The name of the inputs is important as it will be the payload key --&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;"name"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your name"&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;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Your email"&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;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Subscribe&lt;span class="nt"&gt;&amp;lt;/button&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;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"message"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;htmx:afterRequest&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;event&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="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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Display a custom message on request success.&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
                &amp;lt;div class="message"&amp;gt;
                    Thank you, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&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;! You have successfully subscribed with the email: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.
                &amp;lt;/div&amp;gt;
            `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Inject the message into the target element&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The backend
&lt;/h3&gt;

&lt;p&gt;In order to launch Manifest, you need to have NodeJS installed in your machine. From the root of your app, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx add-manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the &lt;code&gt;manifest/backend.yml&lt;/code&gt; file with some dummy content. We replace it with our data model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Newsletter Backend 💌&lt;/span&gt;
&lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Subscribers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can serve the backend with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now use your backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🖥️  Admin Panel:  &lt;a href="http://localhost:1111" rel="noopener noreferrer"&gt;http://localhost:1111&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 API Doc: &lt;a href="http://localhost:1111/api" rel="noopener noreferrer"&gt;http://localhost:1111/api&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It you open your frontend with your browser, you can now subscribe to the newsletter, and verify in the admin panel that the new subscriber has been added to the collection !&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We successfully created a full-stack app with 2 minimalist components: HTMX and Manifest with few lines of code. &lt;/p&gt;

&lt;p&gt;One important point is that we need to use a bit of JavaScript to modify the DOM as HTMX expects HTML response from the server and Manifest delivers JSON through its REST API. Once that is clarified, the connection between front and back is really smooth.&lt;/p&gt;

</description>
      <category>htmx</category>
      <category>manifest</category>
      <category>fullstack</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Svelte + Manifest = Giving Svelte a proper backend with 7 lines of code 🧡🦚</title>
      <dc:creator>Bruno Pérez</dc:creator>
      <pubDate>Mon, 07 Oct 2024 15:50:00 +0000</pubDate>
      <link>https://dev.to/bd_perez/svelte-manifest-giving-svelte-a-proper-backend-with-7-lines-of-code-j6a</link>
      <guid>https://dev.to/bd_perez/svelte-manifest-giving-svelte-a-proper-backend-with-7-lines-of-code-j6a</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR: Using Manifest as backend for Svelte turns it into a full stack application with minimal coding. Check the &lt;a href="https://github.com/brunobuddy/backend-svelte-todo-manifest" rel="noopener noreferrer"&gt;source code on github&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# manifest/backend.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My TODO App ✅&lt;/span&gt;
&lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Todo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;seedCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;completed&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;boolean&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; is an amazing framework with a very simple and elegant syntax. &lt;/p&gt;

&lt;p&gt;However, a frontend is rarely enough to make a functional app: we often need a backend with some logic, a database and and a couple of API endpoints. This can often be time consuming and cumbersome. &lt;/p&gt;

&lt;p&gt;In this post I will show what I think is &lt;strong&gt;the fastest and easiest way of giving a backend for Svelte&lt;/strong&gt;: &lt;a href="https://manifest.build/" rel="noopener noreferrer"&gt;Manifest&lt;/a&gt;. It consists in a single &lt;em&gt;YAML&lt;/em&gt; file (yes, YAML !) that generates a full backend.&lt;/p&gt;

&lt;p&gt;We will create a &lt;strong&gt;todolist app&lt;/strong&gt; with the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding a new &lt;em&gt;todo&lt;/em&gt; stores it in the database with the completed property set to false&lt;/li&gt;
&lt;li&gt;Clicking on the &lt;em&gt;todo&lt;/em&gt; checkbox marks it at complete or uncomplete&lt;/li&gt;
&lt;li&gt;Clicking on the "remove" button deletes the &lt;em&gt;todo&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is what we are building:&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%2Fs4pzkupbq2suu053anvd.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%2Fs4pzkupbq2suu053anvd.png" alt="TODOList Svelte backend screenshot" width="684" height="398"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Create the Svelte app
&lt;/h2&gt;

&lt;p&gt;Svelte has a nice installation script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create svelte@latest my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example I choose the &lt;code&gt;Skeleton project&lt;/code&gt;, in &lt;em&gt;TypeScript&lt;/em&gt; and &lt;em&gt;without any additional options&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We can now run the Svelte frontend with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the backend
&lt;/h2&gt;

&lt;p&gt;We can directly add the Manifest backend from the root of our Svelte app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx add-manifest@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a &lt;code&gt;manifest&lt;/code&gt; folder with a &lt;code&gt;backend.yml&lt;/code&gt; on it. Replace the dummy content with this one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# manifest/backend.yml&lt;/span&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;My TODO App ✅&lt;/span&gt;
&lt;span class="na"&gt;entities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Todo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;seedCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;completed&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;boolean&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now run our backend server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run manifest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now connect to your &lt;strong&gt;admin panel&lt;/strong&gt; and see your &lt;strong&gt;OpenAPI documentation&lt;/strong&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.amazonaws.com%2Fuploads%2Farticles%2Fv5nuqbm9ebw9ud5e7itv.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%2Fv5nuqbm9ebw9ud5e7itv.png" alt="Manifest backend login page" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the client and the server
&lt;/h2&gt;

&lt;p&gt;Now we need to add the app logic and get the data from our backend. First of all intall the Manifest SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i @mnfst/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it short we will put all the logic in the &lt;code&gt;src/routes/+page.svelte&lt;/code&gt; file. On bigger projects it is recommended to have a better &lt;strong&gt;separation of concerns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We will create our 3 functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;getTodos: fetch all the todos&lt;/li&gt;
&lt;li&gt;addTodo: Adds an uncomplete todo to the DB, &lt;/li&gt;
&lt;li&gt;removeTodo: Removes a todo from the DB&lt;/li&gt;
&lt;li&gt;ToggleTodoCompletion: Toggles the completion status of an existing todo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lets start by defining what a Todos is:&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="c1"&gt;// Define the Todo type.&lt;/span&gt;
&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt; &lt;span class="o"&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="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;todos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Todo&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can create our functions:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Manifest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mnfst/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Initialize the Manifest SDK.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;manifest&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;Manifest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch all todos from the database.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTodos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Add a new todo to the database.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addTodo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newTodo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Fetch all todos again to update the list&lt;/span&gt;
  &lt;span class="nf"&gt;getTodos&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Remove a todo from the database.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;removeTodo&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="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;delete&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="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;todo&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;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Toggle the completion status of a todo.&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toggleTodoCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update the todo in the database.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedTodo&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="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;todo&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;manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todo&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="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Reassign the array to trigger update in the UI.&lt;/span&gt;
&lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&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;t&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;updatedTodo&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="nx"&gt;updatedTodo&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&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;h2&gt;
  
  
  Putting everything together
&lt;/h2&gt;

&lt;p&gt;Checkout the &lt;a href="https://github.com/brunobuddy/backend-svelte-todo-manifest/blob/master/src/routes/%2Bpage.svelte" rel="noopener noreferrer"&gt;+page.svelte file&lt;/a&gt; on the demo repository to see the style and UI implementation. We managed to add simply a backend for this Svelte demo app ! Let me know your thoughts about this implementation ! What backend stack do you use for your backends usually ?&lt;/p&gt;

&lt;p&gt;If you liked using Manifest, &lt;a href="https://github.com/mnfst/manifest" rel="noopener noreferrer"&gt;give us a ⭐ on Github&lt;/a&gt; to follow to projects and help us improving it. We also have some &lt;a href="https://github.com/mnfst/manifest/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest" rel="noopener noreferrer"&gt;hacktoberfest issues 🎃&lt;/a&gt; for contributors among you !&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>fullstack</category>
      <category>todo</category>
      <category>manifest</category>
    </item>
  </channel>
</rss>
