<?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>Forem: tom-takeru</title>
    <description>The latest articles on Forem by tom-takeru (@tom-takeru).</description>
    <link>https://forem.com/tom-takeru</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%2F2470661%2F77a17aa4-7819-46e2-ba05-5db72b7abd11.jpeg</url>
      <title>Forem: tom-takeru</title>
      <link>https://forem.com/tom-takeru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tom-takeru"/>
    <language>en</language>
    <item>
      <title>Raise UX Cost-Effectively with Smart Default Values</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Thu, 02 Apr 2026 00:14:27 +0000</pubDate>
      <link>https://forem.com/tom-takeru/raise-ux-cost-effectively-with-smart-default-values-1pe7</link>
      <guid>https://forem.com/tom-takeru/raise-ux-cost-effectively-with-smart-default-values-1pe7</guid>
      <description>&lt;p&gt;Thoughtful defaults are a high-leverage way to lift UX while keeping delivery costs down. You can avoid extra settings screens or branching logic and simply lighten user decisions. Here’s why defaults work and practical examples you can ship today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why defaults work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduce decision cost&lt;/strong&gt;: People follow what is framed as “recommended” or “already selected.” One fewer click can materially shift drop-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower downside risk&lt;/strong&gt;: Safe initial values keep mistakes from causing serious damage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shorten learning time&lt;/strong&gt;: A good default shows “do this first,” so instructions can stay short.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick wins with defaults (with examples)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pre-fill forms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Example: Auto-select country/region from the user’s IP. If you’re right 90% of the time, most users never touch it.&lt;/li&gt;
&lt;li&gt;Example: Default start date to “today” and auto-calc end date as start date +1 day.&lt;/li&gt;
&lt;li&gt;Example: Reuse profile data to pre-fill name and email whenever possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Communicate freemium limits via initial settings
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Example: Make “5 per day” the default selection and label it “Free.” Steering by initial value often feels less pushy than text-only upsells.&lt;/li&gt;
&lt;li&gt;Example: Default heavy batch jobs to "run overnight" to smooth system load while giving users a benefit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Bias notifications toward “important only”
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Example: For SaaS email, default to alerts/security on and marketing off. Turning promotions on later is an active choice and feels less spammy.&lt;/li&gt;
&lt;li&gt;Example: Default push to “digest once a day,” with only urgent items sent immediately. Lower frequency tends to lift NPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Use safety-first defaults to prevent accidents
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Example: For destructive actions (delete/overwrite), default to “show preview.” Require a confirmation phrase to disable it.&lt;/li&gt;
&lt;li&gt;Example: For billing actions, default to “show confirmation dialog” with a short summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Ship a recommended preset as the initial path
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Example: During onboarding, let users choose “Recommended preset” to set notifications, theme, and shortcuts in one go; fine-tuning can wait.&lt;/li&gt;
&lt;li&gt;Example: In B2B settings, offer presets like “security-first,” “ops-simple,” and “dev-friendly,” with the safest one as default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist to measure cost-effectiveness
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the default fit the current user mix?&lt;/strong&gt; Review region, plan, and device distribution to pick what helps the majority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is overriding easy?&lt;/strong&gt; Users who need a different value should see a clear path to change it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does behavior communicate intent better than copy?&lt;/strong&gt; Aim for defaults that show the intent without heavy labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is risk biased toward safety?&lt;/strong&gt; Defaults should prevent severe incidents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you observe and iterate?&lt;/strong&gt; Track how default changes move business metrics.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Defaults are “one-time decisions that pay forever.” Before adding new settings, ask whether an intentional default can raise satisfaction for less build cost. For today’s release, start by questioning the defaults.&lt;/p&gt;

</description>
      <category>ux</category>
      <category>product</category>
      <category>defaults</category>
    </item>
    <item>
      <title>How Software Development Changes in the AI Era</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Thu, 19 Mar 2026 00:50:48 +0000</pubDate>
      <link>https://forem.com/tom-takeru/how-software-development-changes-in-the-ai-era-2bdo</link>
      <guid>https://forem.com/tom-takeru/how-software-development-changes-in-the-ai-era-2bdo</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI drastically reduces implementation cost, but it does not make software development itself unnecessary.&lt;/li&gt;
&lt;li&gt;The skills that gain value are requirements definition, task decomposition, design, verification, and deciding what to build.&lt;/li&gt;
&lt;li&gt;Observing the real world and spotting problems from firsthand experience remains a distinctly human strength.&lt;/li&gt;
&lt;li&gt;Developers are moving from "the person who writes everything by hand" toward "the person who designs an AI-assisted development system and safeguards quality."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Over roughly the past year, AI-assisted development has clearly entered a new phase. It used to be mostly about slightly better autocomplete. Now it can handle meaningful implementation, refactoring, test additions, and even review support.&lt;/p&gt;

&lt;p&gt;That shift often triggers extreme takes such as, "Will humans stop writing code altogether?" But when I look at what actually happens in day-to-day development, reality feels more grounded. The cost of writing code has definitely dropped, but that has only made other bottlenecks more visible.&lt;/p&gt;

&lt;p&gt;In this article, I want to sort out what changes in software development in the AI era, what does not change, and what skills are worth strengthening now. In particular, I believe the ability to observe real friction, notice discomfort, and identify genuine problems will remain one of the most important human roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest shift is lower implementation cost
&lt;/h2&gt;

&lt;p&gt;The largest change AI has brought is lower implementation cost.&lt;/p&gt;

&lt;p&gt;Small CRUD features, rough UI scaffolding, minor changes that follow existing patterns, extra test cases, and the early steps of library migrations all move faster than before. In many cases, it is already quicker to let AI produce a first draft, review it, and tighten only the important parts than to write everything from scratch.&lt;/p&gt;

&lt;p&gt;More recently, AI has gone beyond simple code completion. In some cases, a single instruction is enough to move from an idea to a basic service prototype. It can generate a rough UI, API, data model, and minimum interaction flow in one pass, which has clearly lowered the barrier to getting something tangible in front of your hands.&lt;/p&gt;

&lt;p&gt;In other words, the relative scarcity of "writing code" itself is going down.&lt;/p&gt;

&lt;p&gt;But lower implementation cost and easier software creation are not the same thing. If implementation gets faster, the next set of questions becomes heavier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should we build in the first place?&lt;/li&gt;
&lt;li&gt;How much of it should we build now?&lt;/li&gt;
&lt;li&gt;How should we split the work so it stays safe?&lt;/li&gt;
&lt;li&gt;How do we know the generated code is actually correct?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI era rewards the people who are strong at those questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The center of gravity shifts from writing to deciding
&lt;/h2&gt;

&lt;p&gt;In the future, what matters more is not moving your hands quickly. It is making sound decisions quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deciding what to build
&lt;/h3&gt;

&lt;p&gt;AI can turn instructions into something concrete at impressive speed. But if the problem definition is sloppy, the output will be sloppy too.&lt;/p&gt;

&lt;p&gt;What is the user problem? What is the real bottleneck worth solving right now? Where is the boundary between "we should do this this month" and "not yet"? If those judgments are wrong, even great AI just takes you in the wrong direction faster.&lt;/p&gt;

&lt;p&gt;The starting point for good problem framing is noticing what is actually frustrating for users, what causes friction in the field, and what feels awkward when you touch the product yourself. Small operational annoyances, interface discomfort, and complaints that casually leak out in conversation are hard to discover from a spreadsheet alone. Observing the field and finding problems from firsthand information remains a very human job.&lt;/p&gt;

&lt;p&gt;As development speed increases, the value of deciding product direction increases with it. Compared with a world where implementation skill alone created more differentiation, the decision of what to build becomes a much more direct source of competitive advantage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking work down
&lt;/h3&gt;

&lt;p&gt;If you hand AI a task that is too large, it often produces output that sounds plausible but is risky. On the other hand, it performs very well when the scope is explicit, the constraints are clear, and the checkpoints are well defined.&lt;/p&gt;

&lt;p&gt;That means the people who can break a large problem into small, safe pieces become much stronger.&lt;/p&gt;

&lt;p&gt;For example, it becomes more important to separate work like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock the spec first&lt;/li&gt;
&lt;li&gt;Define API boundaries first&lt;/li&gt;
&lt;li&gt;Build only the UI first&lt;/li&gt;
&lt;li&gt;Separate data migration from presentation changes&lt;/li&gt;
&lt;li&gt;Prepare the verification path first&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This skill mattered before AI too, but now that AI can act like an implementation partner, its value is much easier to see. People who decompose work well can raise the throughput of an entire team.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design and architectural understanding
&lt;/h3&gt;

&lt;p&gt;AI is good at producing locally reasonable code, but it does not naturally guarantee long-term architectural consistency.&lt;/p&gt;

&lt;p&gt;That is why humans need to understand the whole structure. Where should a responsibility live? In which layer should a piece of logic go? Will this change make the system harder to extend six months from now? Those questions matter more now, not less.&lt;/p&gt;

&lt;p&gt;To decide whether AI-generated changes are acceptable, you need to evaluate more than local correctness. You need to judge whether they fit the system as a whole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification becomes more of a bottleneck than implementation
&lt;/h2&gt;

&lt;p&gt;One thing I feel strongly in AI-assisted development is that verification is heavier than implementation.&lt;/p&gt;

&lt;p&gt;Code appears much faster than before. But the cost of confirming that the result is actually safe, does not break existing behavior, and covers edge cases has not disappeared to the same degree. Tools such as Playwright can automate some interactions, but in my experience they are still not at a level where I can trust them with everything.&lt;/p&gt;

&lt;p&gt;For example, I sometimes use AI to produce the first draft of a large-scale refactor. The code comes out quickly, but the real time sink is verifying through E2E checks that the change has not introduced defects across screens and key flows. When the blast radius is wide, a human still has to touch the system carefully and confirm it has not broken.&lt;/p&gt;

&lt;p&gt;Cross-cutting changes are even trickier. When you revise the boundary between free and paid plans across the product and fine-tune edge cases, you are dealing with a high-importance change that requires understanding the whole system. AI can update conditional branches in multiple screens and APIs very quickly, but if you do not understand the overall product behavior, you cannot correctly point out what is missing or inconsistent. In the end, a human still has to inspect the free/paid boundaries in detail before the change feels safe to ship.&lt;/p&gt;

&lt;p&gt;As AI increases the amount of change a team can produce, a few issues move to the front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weak tests make it hard to merge with confidence&lt;/li&gt;
&lt;li&gt;Ambiguous specs make review standards fuzzy&lt;/li&gt;
&lt;li&gt;Weak monitoring makes production issues harder to notice&lt;/li&gt;
&lt;li&gt;A lack of small release units makes every change riskier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the teams that win in the AI era are not the teams that can generate the most code. They are the teams that can move fast without breaking trust and can learn quickly from what they ship.&lt;/p&gt;

&lt;p&gt;That is why investments in tests, CI, type systems, safe deployment patterns, feature flags, and observability become even more important. The faster AI can run, the more valuable guardrails become.&lt;/p&gt;

&lt;h2&gt;
  
  
  Individuals and small teams gain much more leverage
&lt;/h2&gt;

&lt;p&gt;The people who benefit most from AI are often individual builders and small teams, where decision-making and implementation sit close together.&lt;/p&gt;

&lt;p&gt;The reason is simple: the closer the person deciding what to build is to the person prompting AI and making the final call, the shorter the loop becomes. You can decide, prototype, try it, revise it, and change direction very quickly.&lt;/p&gt;

&lt;p&gt;Until recently, one of the biggest walls was, "I have an idea, but implementation is too heavy." That wall is lower now. As a result, planning ability, user understanding, and persistence connect more directly to outcomes. In an extreme case, you can go from a single prompt to a working prototype and start touching it almost immediately.&lt;/p&gt;

&lt;p&gt;In individual development especially, you can start directly from inconveniences you feel yourself and small frustrations you run into repeatedly in daily life. That kind of firsthand information is powerful. Problems you discover by using, observing, and talking to people change the quality of the instructions you give AI.&lt;/p&gt;

&lt;p&gt;An individual can now build a surprisingly strong product in a short time. That makes this a very interesting era for developers. At the same time, the mere fact that something can be built is becoming less of a differentiator, so what matters more is whose problem you solve and how well you solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes at different stages of development experience
&lt;/h2&gt;

&lt;p&gt;If AI exists, does that make foundational learning unnecessary? I do not think so. By "different stages" here, I mean stages of experience and responsibility as a software developer, not stages of AI-tool proficiency. The skills that matter simply show up differently at each stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Early-stage developers
&lt;/h3&gt;

&lt;p&gt;Early-stage developers need the fundamentals to avoid trusting AI output blindly. If you do not understand what an error means, how HTTP and databases work, what types do, how state is managed, or how testing works, you cannot really evaluate AI suggestions.&lt;/p&gt;

&lt;p&gt;AI makes it easier to get moving, but it actually increases the importance of foundational understanding. Without that base, you can easily mass-produce broken code that merely appears to work. A good target is to reach the point where you can explain AI-generated code in your own words.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mid-level developers
&lt;/h3&gt;

&lt;p&gt;Mid-level developers may have the biggest upside. Once you can read an existing system, think through the impact of a change, and hand AI the right-sized task, your productivity can jump significantly.&lt;/p&gt;

&lt;p&gt;At this stage, the differentiator is not "I can write everything myself." It is "I know what to delegate to AI and what I need to own personally." The ability to split work into small requests and quickly judge whether a diff is sound becomes pure leverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Senior developers
&lt;/h3&gt;

&lt;p&gt;Senior developers will be valued even more for architectural judgment, prioritization, quality standards, and designing the team’s overall development system.&lt;/p&gt;

&lt;p&gt;In the AI era, a senior engineer is not only someone who can implement the hardest parts personally. It is also someone who can create an environment where both humans and AI work well. People who can design review standards, documentation, task-splitting strategy, and verification paths become especially strong. The value shifts from individual speed toward building a team that can move fast consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  What developers should start practicing now
&lt;/h2&gt;

&lt;p&gt;The following habits will make it much easier to operate well in AI-assisted development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the habit of writing specs clearly
&lt;/h3&gt;

&lt;p&gt;If something exists only in your head, it does not transfer well to AI or to teammates. Writing down requirements, constraints, completion criteria, and non-goals, even briefly, matters a lot.&lt;/p&gt;

&lt;p&gt;I think writing a good spec is more fundamental than writing a good prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Work in smaller slices
&lt;/h3&gt;

&lt;p&gt;AI makes it tempting to push through one giant change, but in practice, smaller units are safer. They are easier to review, easier to recover from when something breaks, and better for learning.&lt;/p&gt;

&lt;p&gt;That was already true for human-only development. AI makes the benefit even bigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build verification first
&lt;/h3&gt;

&lt;p&gt;Tests, types, linting, E2E checks, and monitoring are all ways to avoid trusting output too easily. The more you use AI, the faster those investments pay off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deepen your understanding beyond code
&lt;/h3&gt;

&lt;p&gt;The people who stay strong in AI-assisted development are the ones who understand the context outside the code: user problems, business domains, operations, billing, support, and organizational decision-making.&lt;/p&gt;

&lt;p&gt;It also matters to actively go out and find problems yourself. Numbers and request lists only tell part of the story. Observing the field, using the product yourself, listening to user reactions, and experiencing the inconvenience firsthand give you a level of resolution that is hard to recover later just by reading a spec.&lt;/p&gt;

&lt;p&gt;In the end, software is not judged only by how elegant the code looks. It is judged by how well it solves real problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Some people who made it this far may be thinking, "If the barrier to building is getting lower in the AI era, maybe I should turn my own ideas into something real."&lt;/p&gt;

&lt;p&gt;I have always loved board games, and I make them myself as well. Through that work, I kept running into the same problem: I wanted to turn ideas into prototypes quickly, but preparation was heavy, and it was hard to reflect test-play feedback fast enough.&lt;/p&gt;

&lt;p&gt;That connects directly to the argument in this article. What matters is not just "being able to build," but how quickly you can turn a problem into something tangible, get feedback, improve it, and repeat the loop.&lt;/p&gt;

&lt;p&gt;That is why I am building &lt;a href="https://kibako.habitat-hub.com/" rel="noopener noreferrer"&gt;KIBAKO&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;KIBAKO is a service for quickly iterating on board game prototyping, collaborative editing, and test play online. I want to make the loop lighter: turn an idea into something playable, try it, improve it, and try again.&lt;/p&gt;

&lt;p&gt;If you are interested in board game creation or fast prototyping, feel free to take a look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If I had to summarize the shift of the AI era in one sentence, it would be this: the center of gravity in developer value is moving from "being able to write" to "being able to move things forward correctly."&lt;/p&gt;

&lt;p&gt;Implementation skill is still necessary, of course. Hard problems will continue to require deep technical ability. But in everyday development, the weight shifts more and more toward defining requirements, decomposing work, verifying both AI and human output, and steering the work in the right direction.&lt;/p&gt;

&lt;p&gt;And some jobs remain stubbornly human. Observing the world, noticing friction, and identifying the real problem is one of them. I believe that part becomes even more important in the AI era.&lt;/p&gt;

&lt;p&gt;The people who will be strongest are not just the ones who know how to use AI well. They are the ones who can design the entire development process around AI while still finding real problems through human observation and judgment.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>career</category>
    </item>
    <item>
      <title>Why The Art of Readable Code Boosts Your English</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Tue, 11 Nov 2025 22:52:13 +0000</pubDate>
      <link>https://forem.com/tom-takeru/why-the-art-of-readable-code-boosts-your-english-969</link>
      <guid>https://forem.com/tom-takeru/why-the-art-of-readable-code-boosts-your-english-969</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code is written for future readers—your teammates, your future self, and even AI—so clarity matters more than cleverness.&lt;/li&gt;
&lt;li&gt;Choosing clear English names nudges you to research vocabulary, turning everyday coding into language practice.&lt;/li&gt;
&lt;li&gt;Reading &lt;em&gt;The Art of Readable Code&lt;/em&gt; naturally builds the desire to write clearer code.

&lt;ul&gt;
&lt;li&gt;As a result, reading &lt;em&gt;The Art of Readable Code&lt;/em&gt; boosts your English skills.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code assumes someone will read it
&lt;/h2&gt;

&lt;p&gt;A program is rarely "write once and forget." You will revisit it months later, collaborators will debug it, and AI tools will analyze it. When names and structures convey intent instantly, everyone saves time and avoids mistakes.&lt;/p&gt;

&lt;p&gt;Think of names as short messages. Labels like &lt;code&gt;data1&lt;/code&gt; hide meaning, while &lt;code&gt;pendingUsers&lt;/code&gt; tells the next reader exactly what to expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming doubles as English training
&lt;/h2&gt;

&lt;p&gt;Using natural English is the best way to give code meaningful names. Romanized Japanese often confuses global teammates and future contributors.&lt;/p&gt;

&lt;p&gt;Suppose you need to represent a list of users waiting for approval. Brainstorming phrases like &lt;code&gt;pendingUsers&lt;/code&gt; or &lt;code&gt;unapprovedAccounts&lt;/code&gt; pushes you to explore related vocabulary. Because the words are tied to a real task, they stick far better than flashcard memorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn the basics with &lt;em&gt;The Art of Readable Code&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;The Art of Readable Code&lt;/em&gt; (published in Japanese as 『リーダブルコード』) is a concise guide to writing code that communicates. Each chapter covers a small habit—naming, comments, decomposing functions—making it approachable for beginners.&lt;/p&gt;

&lt;p&gt;As you read, you internalize the question, "How can I help the next reader?" That mindset naturally leads you to refine English names and look up better phrasing, reinforcing both coding and language skills.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/the-art-of/9781449318482/" rel="noopener noreferrer"&gt;https://www.oreilly.com/library/view/the-art-of/9781449318482/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code clarity serves you, your teammates, and AI assistants alike.&lt;/li&gt;
&lt;li&gt;Intentional English naming turns daily development into continuous language study.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;The Art of Readable Code&lt;/em&gt; is an ideal starting point for aspiring engineers who want readable software and stronger English.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start building the habit today: aim for code that is easy to read, and let the desire for better names guide your English learning.&lt;/p&gt;

</description>
      <category>readability</category>
      <category>naming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Ship Small, Learn Big—Staged Rollouts Strengthen Your Product</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Sun, 26 Oct 2025 23:01:23 +0000</pubDate>
      <link>https://forem.com/tom-takeru/ship-small-learn-big-staged-rollouts-strengthen-your-product-4ip9</link>
      <guid>https://forem.com/tom-takeru/ship-small-learn-big-staged-rollouts-strengthen-your-product-4ip9</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ship smaller slices so users feel the value sooner and you can learn before over-investing.&lt;/li&gt;
&lt;li&gt;Evaluate each cut by whether it delivers standalone value and avoids confusing users.&lt;/li&gt;
&lt;li&gt;Design the slices, refine them during implementation, and iterate quickly with real feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article lays out why delivering in small increments matters, how to decide where to draw the boundaries, and what execution pattern keeps those releases delivering value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why staged rollouts matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unlock value faster by shipping in smaller slices
&lt;/h3&gt;

&lt;p&gt;Bundling everything into one launch keeps even finished functionality sitting on the shelf, pushing back the moment value reaches your users. Shipping a partial slice lets you invite them to try it without delay.&lt;/p&gt;

&lt;p&gt;Letting people touch the product earlier surfaces real reactions right away, so the team can plan its next move with evidence. It also reduces the risk of overbuilding and having to undo work later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let early feedback steer you
&lt;/h3&gt;

&lt;p&gt;Watching how people use the slice you shipped makes it easier to decide whether to double down or shift priorities. You stay away from slamming the brakes right before a launch, which protects both the team’s schedule and the user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevent big-bang release disasters
&lt;/h3&gt;

&lt;p&gt;All-at-once launches widen the blast radius when something goes wrong and draw out recovery times. Rolling out in stages limits how many people feel the regression and lets you focus monitoring and support where the change actually happened.&lt;/p&gt;

&lt;p&gt;When each slice bakes in a risk review and clear rollback path, you can unwind issues quickly without denting user trust. The team stays confident and keeps improving the product deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Staged rollouts are like growing a tree
&lt;/h2&gt;

&lt;p&gt;A staged rollout is like nurturing a product as you would a tree.&lt;br&gt;&lt;br&gt;
You plant a small seedling (the first feature), observe its growth (user feedback), prune the branches (remove unnecessary parts), and add fertilizer (make improvements). Step by step, it grows stronger and healthier.&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%2Fxm2afpz1w2cwyzz9g5rp.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%2Fxm2afpz1w2cwyzz9g5rp.png" alt="A flat-style digital illustration showing a potted plant with leaves shaped like UI elements, being watered, pruned, and fertilized by human hands — symbolizing staged rollouts and iterative software growth." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to decide on the release units
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can it deliver value on its own?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It's crucial to consider whether the slice can solve a complete, albeit small, user problem and be perceived as a useful improvement.&lt;/li&gt;
&lt;li&gt;If a feature only provides value when paired with something else, you need to map out those dependencies and adjust the release order accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Will upcoming changes confuse your users?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;If you expect to update the feature again soon, will that follow-up change create confusion for your users?&lt;/li&gt;
&lt;li&gt;In other words, keep the rollout as small as possible unless doing so would violate these guardrails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Is its impact measurable?&lt;/li&gt;
&lt;li&gt;Ease of rollback&lt;/li&gt;
&lt;li&gt;Ease of marketing and communication&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Steps for running a staged rollout
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sketch shippable slices during discovery
&lt;/h3&gt;

&lt;p&gt;Product managers, engineers, and designers should sketch the units they believe can reach users early. Whether you cut the work by page, flow, or another lens, having those user-facing slices defined up front keeps later conversations smooth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-scope during estimation to keep it realistic
&lt;/h3&gt;

&lt;p&gt;Bring in engineering’s view of how the implementation will go. That is where you learn “these pieces should ship together” or “this part can be fully isolated.” Reconfirm priorities and resources, then lock in the release phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use feature flags to meter exposure
&lt;/h3&gt;

&lt;p&gt;Feature flags are runtime toggles that wrap new features, allowing for staged releases where you can switch features on or off without redeploying and enable them for specific users (e.g., internal-only or developers-only) as needed. They let you target slices to specific cohorts and expand coverage incrementally. That same toggle keeps risk low while still delivering value to the people you turn it on for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ship small, listen, and fold the learning back in
&lt;/h3&gt;

&lt;p&gt;For each slice, move from implementation to testing to production in a tight loop. Right after each release, watch dashboards and support channels, and fold the reactions into the next improvement. Keeping the loop anchored in user feedback makes the value of staged rollouts obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shipping valuable functionality in the smallest possible increments benefits both users and the development team.&lt;/li&gt;
&lt;li&gt;Looking at standalone benefit and dependency management helps you efficiently define release units.&lt;/li&gt;
&lt;li&gt;Releasing in stages while folding real reactions into each iteration turns the need for staged rollouts into something you feel every day.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productmanagement</category>
      <category>agile</category>
      <category>releasestrategy</category>
    </item>
    <item>
      <title>My AI-Era Technical Article Repository</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Sun, 28 Sep 2025 07:21:57 +0000</pubDate>
      <link>https://forem.com/tom-takeru/my-ai-era-technical-article-repository-3eao</link>
      <guid>https://forem.com/tom-takeru/my-ai-era-technical-article-repository-3eao</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I keep English posts for dev.to and Japanese posts for Qiita in one GitHub repo, then lean on an AI-first writing flow to move faster🚀&lt;/li&gt;
&lt;li&gt;Make targets wrap TypeScript scripts so draft updates and publications reuse front matter settings without risky manual toggles.&lt;/li&gt;
&lt;li&gt;This repository is just my working example, so treat the structure and workflow as inspiration and remix them for your own stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I wanted to draft articles entirely inside my favorite AI editor, teach the model how I structure posts, and shorten the handoff from draft to publication—this repository is how I made that happen📚&lt;/p&gt;

&lt;p&gt;Feel free to peek at &lt;a href="https://github.com/tom-takeru/articles" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/articles&lt;/a&gt; as you read. The setup routes Japanese pieces to &lt;a href="https://qiita.com/" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt; and English pieces to &lt;a href="https://dev.to/"&gt;dev.to&lt;/a&gt; so each audience gets the right language. Qiita ships an &lt;a href="https://github.com/increments/qiita-cli" rel="noopener noreferrer"&gt;official CLI&lt;/a&gt;, but I skipped it here. dev.to currently lacks an official command-line client.&lt;/p&gt;

&lt;p&gt;Pick the ingredients that help your workflow and rearrange the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Writers who want AI-first editing
&lt;/h3&gt;

&lt;p&gt;You prefer loading Markdown into an AI editor and iterating with prompts instead of jumping between browser tabs🖊️&lt;/p&gt;

&lt;h3&gt;
  
  
  Writers who want to train their AI on house style
&lt;/h3&gt;

&lt;p&gt;Keeping past articles and new drafts in one repo makes it easy to surface voice, format, and go-to sections for your assistant🧠&lt;/p&gt;

&lt;h3&gt;
  
  
  Writers who want Git-powered article history
&lt;/h3&gt;

&lt;p&gt;Git history doubles as your publishing log, which makes diffs and reviews painless when you need approval🗂️&lt;/p&gt;

&lt;h3&gt;
  
  
  Writers shipping bilingual posts efficiently
&lt;/h3&gt;

&lt;p&gt;You plan to publish Japanese to Qiita and English to dev.to in pairs, and you want automation to prevent duplicated work🌍&lt;/p&gt;

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

&lt;p&gt;Here’s a quick tour of &lt;a href="https://github.com/tom-takeru/articles" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/articles&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown lives under &lt;code&gt;content/ja/&lt;/code&gt; and &lt;code&gt;content/en/&lt;/code&gt;, and each file’s &lt;code&gt;platform&lt;/code&gt; field keeps the correct destination aligned🗃️&lt;/li&gt;
&lt;li&gt;TypeScript scripts in &lt;code&gt;scripts/&lt;/code&gt; parse front matter and talk to the dev.to and Qiita APIs for draft and publish operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.posts-map.*.json&lt;/code&gt; files synchronize local Markdown with remote IDs so the Makefile can run exactly the steps you need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Publishing workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 Draft the article
&lt;/h3&gt;

&lt;p&gt;Edit your Markdown with help from your AI assistant and keep momentum with tight prompt-feedback loops✍️&lt;/p&gt;

&lt;p&gt;Existing posts in the repo become on-demand references for tone and structure, and once the Japanese version feels right you can ask the AI for an English translation before reviewing it yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 Create or refresh platform drafts
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;make draft&lt;/code&gt; to update dev.to and Qiita drafts🧪&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PUBLISH_MODE&lt;/code&gt; defaults to &lt;code&gt;draft&lt;/code&gt;, so you won’t accidentally ship a half-finished article.&lt;/p&gt;

&lt;h4&gt;
  
  
  If you need to embed images
&lt;/h4&gt;

&lt;p&gt;Both Qiita and dev.to require manual uploads from their draft editors. The flow here is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;make draft&lt;/code&gt; to create the remote draft.&lt;/li&gt;
&lt;li&gt;Upload images in each platform’s web UI.&lt;/li&gt;
&lt;li&gt;Copy the generated URLs into your local Markdown.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;make draft&lt;/code&gt; again to sync the updated content.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3 Publish the article
&lt;/h3&gt;

&lt;p&gt;Once everything reads clean, run &lt;code&gt;make publish&lt;/code&gt;🎯&lt;/p&gt;

&lt;p&gt;The workflow refuses to publish if a draft was never created, which guards against accidental releases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 Merge changes into the main branch on GitHub
&lt;/h3&gt;

&lt;p&gt;Commit your changes, push the branch, and open a PR💾&lt;/p&gt;

&lt;p&gt;CI fetches the current publication status via API using the &lt;code&gt;.posts-map.*.json&lt;/code&gt; records generated during your local runs. That safety net keeps forgotten draft or publish commands from slipping through.&lt;/p&gt;

&lt;p&gt;When the checks pass, merge the PR into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you need more detail, browse the README and source files in &lt;a href="https://github.com/tom-takeru/articles" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/articles&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Pairing an AI editor with a Git-backed workflow turns article production into a tight loop of prompts, reviews, and scripted releases🌱&lt;/p&gt;

&lt;p&gt;From here it’s all about tuning the process so it fits your voice and schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side note
&lt;/h2&gt;

&lt;p&gt;I wrote this article with Codex Web and Codex CLI. Codex Web is my current go-to OpenAI assistant for spinning up isolated development environments in parallel. If you’re curious, check out my write-up: &lt;a href="https://dev.to/tom-takeru/how-i-used-codex-to-ship-nearly-80-pull-requests-in-two-days-34me"&gt;How I Used Codex to Ship Nearly 80 Pull Requests in Two Days&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>technicalwriting</category>
      <category>automation</category>
    </item>
    <item>
      <title>How I Used Codex to Ship Nearly 80 Pull Requests in Two Days</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Sat, 27 Sep 2025 09:38:56 +0000</pubDate>
      <link>https://forem.com/tom-takeru/how-i-used-codex-to-ship-nearly-80-pull-requests-in-two-days-34me</link>
      <guid>https://forem.com/tom-takeru/how-i-used-codex-to-ship-nearly-80-pull-requests-in-two-days-34me</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex is the ultimate development environment if you already pay for ChatGPT Plus—parallel tasks and even mobile development made shipping almost 80 PRs in two days feel realistic.&lt;/li&gt;
&lt;li&gt;Leaning on Codex Web as the hub, Codex CLI for precision edits, and splitting work across multiple environments with Ask-driven ideation sent my throughput skyrocketing.&lt;/li&gt;
&lt;li&gt;Going forward, engineers will compete less on implementation and more on "what to build," driven by planning and architectural insight.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Over two intense days of personal development I created nearly 80 pull requests. This article reorganizes that experience so you can see, at a glance, why Codex Web and Codex CLI together made it possible—from the background and motivation to the workflow details and what I plan to explore next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background &amp;amp; Self-Introduction
&lt;/h2&gt;

&lt;p&gt;I am a board-game-loving full-stack engineer who constantly experiments with LLM-powered coding assistants. For more details about me, please see &lt;a href="https://tom-takeru.habitat-hub.com" rel="noopener noreferrer"&gt;Getting Started with tom-takeru&lt;/a&gt; and &lt;a href="https://github.com/tom-takeru" rel="noopener noreferrer"&gt;GitHub | @tom-takeru&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is how my current toolkit looks.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Current LLM Toolkit
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Copilot

&lt;ul&gt;
&lt;li&gt;Generate PR descriptions and pair with VS Code (I relied on it heavily for personal projects in the past)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Cursor

&lt;ul&gt;
&lt;li&gt;Used intensively at work until a month or two ago&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Cursor CLI

&lt;ul&gt;
&lt;li&gt;Tried it for a few days right after the ChatGPT 5 release&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Claude Code

&lt;ul&gt;
&lt;li&gt;My primary assistant for work right now&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Codex Web

&lt;ul&gt;
&lt;li&gt;Using it nonstop for the past month on side projects&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Codex CLI

&lt;ul&gt;
&lt;li&gt;Hammering it daily over the past month as well&lt;/li&gt;
&lt;li&gt;I even hit the ChatGPT Plus usage limit screen:&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;CLI output when the limit triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;▌🖐  You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing)
▌or try again in 19
▌hours 59 minutes.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recently released usage overlay (the new 5h and weekly limits are visible):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;╭──────────────────────────────────────────────────────────────────────────╮
&lt;/span&gt;&lt;span class="gp"&gt;│  &amp;gt;&lt;/span&gt;_ OpenAI Codex &lt;span class="o"&gt;(&lt;/span&gt;v0.42.0&lt;span class="o"&gt;)&lt;/span&gt;                                               │
&lt;span class="go"&gt;│                                                                          │
│  Model:          gpt-5-codex (reasoning low, summaries auto)             │
│  Directory:      ~/.config/nvim                                          │
│  Approval:       on-request                                              │
│  Sandbox:        read-only                                               │
&lt;/span&gt;&lt;span class="gp"&gt;│  Agents.md:      &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;                                                  &lt;/span&gt;│
&lt;span class="go"&gt;│  Account:        dummy-email@example.com (Plus)                          │
│  Session:        -dummy-session-id-                                      │
│                                                                          │
│  Token usage:    0 total  (0 input + 0 output)                           │
│  5h limit:       [████████████████████] 100% used (resets 18:31)         │
│  Weekly limit:   [█████████████░░░░░░░] 64% used (resets 19:58 on 1 Oct) |
╰──────────────────────────────────────────────────────────────────────────╯
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Think About Codex
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex is the LLM coding assistant service that perfectly fits my needs.&lt;/li&gt;
&lt;li&gt;It ships both a web interface and a CLI, and Codex Web in particular blew me away with how it handles multi-tasking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Started Using Codex
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code running inside neovim showed me how powerful an LLM CLI could be at work, so I started searching for something I could lean on for side projects.&lt;/li&gt;
&lt;li&gt;When I heard that my existing ChatGPT Plus subscription unlocked Codex CLI, I jumped in immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Codex Changed My Throughput
&lt;/h2&gt;

&lt;p&gt;Subjectively, &lt;strong&gt;my development throughput jumped several times over&lt;/strong&gt;. In the same amount of time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Working with a conventional LLM coding assistant:&lt;/strong&gt; Imagine you’re a single cook in a restaurant. You take an order, cook the dish, serve it, and only then can you start the next order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working with Codex Web:&lt;/strong&gt; Now, imagine you’re the head chef of a large kitchen. You take five orders, hand them off to five different cooks, and they all work at the same time. You just check the dishes before they go out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  My Plan and Cost
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Plan

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://openai.com/chatgpt/pricing" rel="noopener noreferrer"&gt;ChatGPT Plus&lt;/a&gt; only—$20/month with no additional Codex fee&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Coverage

&lt;ul&gt;
&lt;li&gt;Includes ChatGPT, Codex Web, and Codex CLI&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Codex—Especially Codex Web—Stands Out
&lt;/h2&gt;

&lt;p&gt;Combining Codex Web and Codex CLI reshaped how I manage tasks and experience development. Breaking it down highlights where the acceleration comes from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Task Execution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create an Environment by pointing Codex at a repository and it clones the repo into its own isolated workspace.&lt;/li&gt;
&lt;li&gt;Multiple instructions progress simultaneously inside their own sandboxes.&lt;/li&gt;
&lt;li&gt;It genuinely feels like managing a team of engineers from a lead’s cockpit.&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%2Fxkr1m0hwhyrjs165wh90.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%2Fxkr1m0hwhyrjs165wh90.png" alt="Codex Web running multiple tasks in parallel" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Creation Speed
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Review rough drafts in the browser, request fixes if needed, and create a PR (Drafts included) with a single click.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Screens that show the diff review and PR creation experience in Codex Web:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Codex Web displaying task diff after completion&lt;/th&gt;
&lt;th&gt;Pull request generated via the Codex Web Push button&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Ffqy1gcidj439wzjque99.png" alt="Codex Web showing a completed task diff" width="800" height="386"&gt;&lt;/td&gt;
&lt;td&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%2Fh4ywvvn1cimhkcd7zxkv.png" alt="Pull request created from Codex Web Push button" width="800" height="386"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Idea Generation via Ask
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ask Codex for prompts like “Give me three small refactoring ideas,” then hit Start Task to spin them into runnable work immediately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Screens that show how Ask suggestions flow into actual tasks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ask surface listing refactoring ideas&lt;/th&gt;
&lt;th&gt;Starting a task directly from an Ask suggestion&lt;/th&gt;
&lt;th&gt;Codex Web running multiple Ask-derived tasks in parallel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fzru92h9098mh5w8gl97q.png" alt="Ask feature suggesting refactoring ideas" width="800" height="386"&gt;&lt;/td&gt;
&lt;td&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%2Fn5nr4f8mbwfvazpo37r3.png" alt="Starting a task from an Ask suggestion" width="800" height="386"&gt;&lt;/td&gt;
&lt;td&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%2Fdy91o5t2aef0nihr9zkg.png" alt="Codex processing multiple Ask-derived tasks" width="800" height="386"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Development from a Phone
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I used to stop at reviewing diffs in the GitHub app, but now I can inspect diffs and ship changes end-to-end from my phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mobile task list inside the ChatGPT app&lt;/th&gt;
&lt;th&gt;Summary view for a specific task&lt;/th&gt;
&lt;th&gt;Task diff and Push (Create PR) button on mobile&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fwf8gy32tt91se9doq5bp.jpeg" alt="Mobile development experience - Task list in the ChatGPT app" width="800" height="1734"&gt;&lt;/td&gt;
&lt;td&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%2Fww1yk8ivhyk6izyir27h.jpeg" alt="Mobile development experience - Summary view of a task" width="800" height="1734"&gt;&lt;/td&gt;
&lt;td&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%2Fhbboivb1tdmoipbegusv.jpeg" alt="Mobile development experience - Task diff and Push (Create PR) button" width="800" height="1734"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Codex Power-Use Playbook
&lt;/h2&gt;

&lt;p&gt;Codex Web sits at the center while the CLI and prompt design keep the pace high. This is the foundation I rely on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip 1: Lead With Codex Web
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Finish most development tasks entirely in the browser.&lt;/li&gt;
&lt;li&gt;Split instructions by scope so work progresses in parallel without stepping on itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tip 2: Use Codex CLI for Surgical Edits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apply UI tweaks or rapid validation directly from the CLI.&lt;/li&gt;
&lt;li&gt;Clone the repository into multiple directories to run Codex CLI across several environments. You still need to watch for conflicts, and it is easy to hit the Codex CLI quota, so this setup helps save that budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tip 3: Shape Prompts Carefully
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Break requests into “the smallest steps that satisfy the requirement.”&lt;/li&gt;
&lt;li&gt;Define scope boundaries up front to avoid conflicts.&lt;/li&gt;
&lt;li&gt;If conflicts happen anyway, rebase locally and resolve with the CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Case Study: Shipping ~80 PRs in Two Days
&lt;/h2&gt;

&lt;p&gt;This burst happened while building a subscription-style product I am developing. The repository has been active for about six months and has already closed roughly 400 PRs. The work I did this time included dev-environment improvements, library upgrades, UI polish, and mid-sized feature additions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step-by-Step Loop
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Request tasks in parallel

&lt;ul&gt;
&lt;li&gt;Hand Codex Web around five tasks at a time while being mindful of conflict risk.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create PRs

&lt;ul&gt;
&lt;li&gt;Skim the output and, if it looks good, turn it into a PR immediately.&lt;/li&gt;
&lt;li&gt;If something is off, highlight the issues and rerun the task.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Handle CI and reviews

&lt;ul&gt;
&lt;li&gt;When CI finishes, respond to CodeRabbit comments or failures by pulling the remote branch.&lt;/li&gt;
&lt;li&gt;Combine Codex CLI with GitHub MCP so review follow-ups and pushes happen automatically from the PR link.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Merge

&lt;ul&gt;
&lt;li&gt;Once the feedback and CI are clear, run manual verification and merge.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reuse idle time

&lt;ul&gt;
&lt;li&gt;While waiting for reviews or CI, hop back to other PRs and resume from step 3.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Running this loop meant that &lt;strong&gt;I looked up mid-session and realized nearly 80 PRs were ready in just two days.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'll Try Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub reviews via Codex Web

&lt;ul&gt;
&lt;li&gt;Drafts are produced by AI and humans lead reviews today, but I want to try Codex’s review capabilities.&lt;/li&gt;
&lt;li&gt;The newly added &lt;code&gt;/review&lt;/code&gt; command in Codex CLI looks especially promising.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Deeper CLI usage

&lt;ul&gt;
&lt;li&gt;The web app is still my primary driver, yet I am convinced there are hidden gems left in the CLI.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Tool benchmarking

&lt;ul&gt;
&lt;li&gt;I plan to keep using Claude Code and CodeRabbit alongside Codex so each tool can play to its strengths.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reflections
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex feels red-hot right now, with updates seemingly landing every two days.

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/openai/codex/releases" rel="noopener noreferrer"&gt;https://github.com/openai/codex/releases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Thinking hard about “what to build” matters even more.

&lt;ul&gt;
&lt;li&gt;Engineers will win by designing direction, not just writing code.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Codex Web genuinely feels like a manager’s console.

&lt;ul&gt;
&lt;li&gt;It is like coordinating four or five engineers, setting direction, and reviewing their code.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Task decomposition precision is crucial.

&lt;ul&gt;
&lt;li&gt;The finer you split work before handing it to the Codex “engineers,” the better the outcome.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Architecture understanding is valuable.

&lt;ul&gt;
&lt;li&gt;When the human understands the full picture, it is easier to define steps and delegate them.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Right now it still favors engineers.

&lt;ul&gt;
&lt;li&gt;At this stage, engineers squeeze the most value out of the toolset.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The next battleground is deciding what to build.

&lt;ul&gt;
&lt;li&gt;As AI tooling becomes accessible to everyone, the default becomes “you can build anything,” so creativity and planning take center stage.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Codex is the ultimate development environment if you already pay for ChatGPT Plus—no additional fee.&lt;/li&gt;
&lt;li&gt;Pairing Codex Web for direction with Codex CLI for precision edits unlocks a new development rhythm.&lt;/li&gt;
&lt;li&gt;Parallel tasks and mobile development feel like switching from walking to riding a bike—maybe even jumping into a car.&lt;/li&gt;
&lt;li&gt;Shipping roughly 80 PRs in two days is absolutely doable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codex</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Server Monitoring using GAS (Google Apps Script) within the Free Usage Limits</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Sat, 22 Mar 2025 04:52:08 +0000</pubDate>
      <link>https://forem.com/tom-takeru/server-monitoring-using-gas-google-apps-script-within-the-free-usage-limits-14kd</link>
      <guid>https://forem.com/tom-takeru/server-monitoring-using-gas-google-apps-script-within-the-free-usage-limits-14kd</guid>
      <description>&lt;p&gt;When using Google Apps Script (GAS), one common concern is how much you can achieve within the free usage limits. In this article, we'll summarize these limits and demonstrate a practical example of how to prevent your backend server from sleeping by regularly pinging its health-check API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main Limits of GAS Free Tier
&lt;/h2&gt;

&lt;p&gt;Below are the primary limitations of GAS’s free usage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Maximum execution time per script run&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 minutes (360 seconds)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total daily execution time&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90 minutes (5400 seconds)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number of triggers&lt;/td&gt;
&lt;td&gt;Maximum &lt;strong&gt;20 triggers&lt;/strong&gt; per script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL Fetch calls&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;20,000 requests&lt;/strong&gt; per day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emails sent&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;100 emails&lt;/strong&gt; per day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For detailed information, please refer to the official documentation: &lt;a href="https://developers.google.com/apps-script/guides/services/quotas" rel="noopener noreferrer"&gt;Quotas for Google Services (Official)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use GAS for Regular Server Health-Check to Prevent Sleep
&lt;/h2&gt;

&lt;p&gt;In this practical example, we demonstrate how to prevent a backend server running on a free plan from sleeping by pinging its health-check API every 5 minutes using GAS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Steps
&lt;/h3&gt;

&lt;p&gt;Follow these detailed steps to set up the GAS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new Google Spreadsheet.&lt;/li&gt;
&lt;li&gt;Create a sheet named &lt;code&gt;ServerCheckURLs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enter the URLs you want to monitor into column A of this sheet.&lt;/li&gt;
&lt;li&gt;Open the GAS editor by clicking on "Extensions" → "Apps Script" from the spreadsheet.&lt;/li&gt;
&lt;li&gt;Copy and paste the GAS script provided below and save it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  GAS Implementation Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&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;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUrlsFromSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ServerCheckURLs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;Logger&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="s1"&gt;===== HTTP Status of URLs =====&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;urls&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetchHttpStatusCodeWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;Logger&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. URL: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n   HTTP Status: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;statusCode&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="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;Logger&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="s1"&gt;================================&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch URLs from column A of the specified sheet&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUrlsFromSheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetName&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;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SpreadsheetApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getActiveSpreadsheet&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getSheetByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sheetName&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;lastRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLastRow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastRow&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;urlRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`A1:A&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastRow&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;urlRange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;()&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;url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// remove empty cells&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch HTTP status code with retry mechanism&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchHttpStatusCodeWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetchHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Logger&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="s2"&gt;`Retrying &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;intervalMs&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;Utilities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intervalMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetchHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;attempts&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch HTTP status code for a given URL&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchHttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;muteHttpExceptions&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="k"&gt;try&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;UrlFetchApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getResponseCode&lt;/span&gt;&lt;span class="p"&gt;();&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Logger&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="s2"&gt;`Error accessing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&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="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Set up a trigger following the steps below:&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Trigger Setup
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;In the GAS editor, select "Triggers" from the left-hand menu.&lt;/li&gt;
&lt;li&gt;Click on "Add Trigger" button at the bottom-right.&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;main&lt;/code&gt; function to execute.&lt;/li&gt;
&lt;li&gt;Set the trigger type to "Time-driven".&lt;/li&gt;
&lt;li&gt;Choose the timer frequency, such as every 5 minutes, and save the trigger.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practical Example: Evaluating a 5-Minute Interval Trigger
&lt;/h2&gt;

&lt;p&gt;Here's a concrete example scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Script execution frequency&lt;/strong&gt;: Every 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily execution count&lt;/strong&gt;: 24 hours × (60 minutes ÷ 5 minutes) = &lt;strong&gt;288 times/day&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum execution time per run&lt;/strong&gt;: &lt;strong&gt;approximately 6.5 seconds&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Note: The 6.5 seconds represents the longest observed runtime, not the average.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Calculating total daily execution time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;About 6.5 seconds × 288 runs ≈ &lt;strong&gt;1883 seconds (~31 minutes)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result: Within Free Tier Limits
&lt;/h2&gt;

&lt;p&gt;Note: Depending on your setup, you may hit either the execution time limit or URL Fetch limit first. It is advisable to calculate based on your specific number of monitored URLs and their response times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluating URL Fetch Limit
&lt;/h2&gt;

&lt;p&gt;Here's a quick reference table to ensure you're within the daily URL Fetch limit (20,000 requests per day):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number of URLs&lt;/th&gt;
&lt;th&gt;Requests per Run&lt;/th&gt;
&lt;th&gt;Runs per Day (every 5 mins)&lt;/th&gt;
&lt;th&gt;Total Requests per Day&lt;/th&gt;
&lt;th&gt;Within Limit?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;1,440&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;2,880&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;14,400&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;19,872&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;288&lt;/td&gt;
&lt;td&gt;20,160&lt;/td&gt;
&lt;td&gt;❌ No (exceeds limit)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As shown, you can safely monitor up to &lt;strong&gt;69 URLs&lt;/strong&gt; at a 5-minute interval without exceeding the free URL Fetch quota.&lt;/p&gt;

&lt;p&gt;Comparing with GAS’s free limits (6 minutes per run, 90 minutes per day), the current scenario (maximum ~6.5 seconds per run, ~31 minutes per day) is comfortably within the allowed limits.&lt;/p&gt;

&lt;p&gt;Therefore, such regular health checks every 5 minutes can safely and effectively operate within GAS’s free usage tier.&lt;/p&gt;

</description>
      <category>gas</category>
      <category>freeserver</category>
      <category>render</category>
      <category>googleappsscrip</category>
    </item>
    <item>
      <title>Real-Time Web Application demo with WebSocket - Backend</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Fri, 27 Dec 2024 00:55:52 +0000</pubDate>
      <link>https://forem.com/tom-takeru/real-time-web-application-demo-with-websocket-backend-1a1n</link>
      <guid>https://forem.com/tom-takeru/real-time-web-application-demo-with-websocket-backend-1a1n</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In this article, I will explore the backend implementation of my real-time WebSocket application. Built using Gin and Go, the backend efficiently manages WebSocket connections, stores messages, and broadcasts updates to all connected clients.&lt;/p&gt;




&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tom-takeru/web-socket-demo" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/web-socket-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My backend project is organized to ensure modularity and reusability. Below is the updated directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./backend
├── go.mod
├── go.sum
├── main.go
└── stores
    └── messages.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Key Directories and Files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;go.mod&lt;/code&gt;&lt;/strong&gt;: Defines module dependencies and versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main.go&lt;/code&gt;&lt;/strong&gt;: Entry point of the application that initializes the WebSocket server and routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stores/messages.go&lt;/code&gt;&lt;/strong&gt;: Manages message storage with thread-safe operations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Core Component: main.go
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;main.go&lt;/code&gt; is the main entry point for my WebSocket server application. It sets up the Gin router, defines the WebSocket route, and handles the WebSocket lifecycle.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code Walkthrough
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gorilla/websocket"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/tom-takeru/web-socket-demo/backend/stores"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;upgrader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Upgrader&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CheckOrigin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Origin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c"&gt;// NOTE: This project is for local development only.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;origin&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:3000"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;messageStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMessageStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;clients&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clientsMu&lt;/span&gt;    &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handleWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;upgrader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Failed to upgrade to WebSocket"&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="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Send existing messages to the new connection&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;messageStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalMessages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msgData&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RFC3339&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;

        &lt;span class="n"&gt;messageStore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;modifiedMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;clients&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modifiedMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&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="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;clientsMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/ws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handleWebSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:8080"&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;h4&gt;
  
  
  Key Functionalities
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State Management&lt;/strong&gt;: Tracks connected clients and ensures thread-safe access using a mutex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket Lifecycle&lt;/strong&gt;: Handles connection setup, message broadcasting, and cleanup on disconnection.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The backend implementation of my WebSocket demo application demonstrates how to manage real-time communication effectively using Gin and Go. By leveraging WebSocket for persistent connections and a thread-safe message store, this application serves as a robust foundation for building real-time web applications.&lt;/p&gt;

&lt;p&gt;In the next article, I will discuss deployment strategies and optimizing WebSocket performance.&lt;/p&gt;




&lt;h3&gt;
  
  
  Links to the Series
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websockets-3fhb"&gt;Real-Time Web Application Demo with WebSocket - Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websocket-frontend-4kep"&gt;Real-Time Web Application Demo with WebSocket - Frontend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>websocket</category>
      <category>go</category>
    </item>
    <item>
      <title>Real-Time Web Application demo with WebSocket - Frontend</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Fri, 27 Dec 2024 00:42:27 +0000</pubDate>
      <link>https://forem.com/tom-takeru/real-time-web-application-demo-with-websocket-frontend-4kep</link>
      <guid>https://forem.com/tom-takeru/real-time-web-application-demo-with-websocket-frontend-4kep</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In this article, I will explore the frontend implementation of our real-time WebSocket application. Built with Next.js and TypeScript, the frontend serves as an interactive interface for sending and receiving real-time messages. Let's dive into the details of how this application is structured and how the components interact to provide a seamless WebSocket experience.&lt;/p&gt;




&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tom-takeru/web-socket-demo" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/web-socket-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The frontend project is organized to ensure modularity and reusability. Below is the updated directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./frontend/src
├── app
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── components
│   ├── HomePage.tsx
│   ├── message
│   │   ├── MessageInputForm.tsx
│   │   ├── MessageList.tsx
│   │   └── MessageSection.tsx
│   └── websocket
│       ├── WebSocketControls.tsx
│       ├── WebSocketSection.tsx
│       └── WebSocketStatus.tsx
└── hooks
    └── useMessages.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Key Directories and Files
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;app/&lt;/code&gt;&lt;/strong&gt;: Contains global styles and layout definitions for the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;components/&lt;/code&gt;&lt;/strong&gt;: Includes the primary components for messages and WebSocket handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hooks/&lt;/code&gt;&lt;/strong&gt;: Contains custom hooks, such as &lt;code&gt;useMessages&lt;/code&gt;, for managing state and logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;HomePage.tsx&lt;/code&gt;&lt;/strong&gt;: The central page that ties everything together.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Core Component: HomePage.tsx
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;HomePage.tsx&lt;/code&gt; is the main entry point for the WebSocket application. It integrates message and WebSocket-related components while managing the application's state and lifecycle.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code Walkthrough
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&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;react&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;useMessages&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;@/hooks/useMessages&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;MessageSection&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;@/components/message/MessageSection&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;WebSocketSection&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;@/components/websocket/WebSocketSection&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clearMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMessages&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;connectionStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isSending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsSending&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="nf"&gt;useEffect&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="k"&gt;return &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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleOpen&lt;/span&gt; &lt;span class="o"&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;Connected to WebSocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connected&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;handleMessage&lt;/span&gt; &lt;span class="o"&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="nx"&gt;MessageEvent&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;Message from server:&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;data&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;data&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;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;addMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setIsSending&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="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="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;WebSocket error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to connect to WebSocket.&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;handleClose&lt;/span&gt; &lt;span class="o"&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;WebSocket closed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;startWebSocket&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOSED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSocket is already open&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;clearMessages&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;ws&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:8080/ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleOpen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleClose&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;stopWebSocket&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSocket is not open&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="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setConnectionStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&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;sendMessage&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&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 cannot be empty&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebSocket is not open&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setIsSending&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonMessage&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;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;wsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center justify-center min-h-screen p-8 pb-20 sm:p-20 font-sans"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;WebSocket Demo App&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col gap-2 items-center w-full max-w-md"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;MessageSection&lt;/span&gt;
          &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;setMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setMessage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;isDisabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;connectionStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Disconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isSending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WebSocketSection&lt;/span&gt;
          &lt;span class="na"&gt;connectionStatus&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;connectionStatus&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;startWebSocket&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;startWebSocket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;stopWebSocket&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;stopWebSocket&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h4&gt;
  
  
  Key Functionalities
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;State Management&lt;/strong&gt;: Manages connection status, current message, and sending state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket Lifecycle&lt;/strong&gt;: Handles connection setup, events (&lt;code&gt;onopen&lt;/code&gt;, &lt;code&gt;onmessage&lt;/code&gt;, &lt;code&gt;onerror&lt;/code&gt;, &lt;code&gt;onclose&lt;/code&gt;), and teardown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Interaction&lt;/strong&gt;: Provides clear feedback and controls for starting/stopping the connection and sending messages.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;HomePage.tsx&lt;/code&gt; component demonstrates the integration of WebSocket functionalities with a clean React structure. Its focus on state management and user interaction makes it the backbone of the application's frontend.&lt;/p&gt;

&lt;p&gt;In the next article, I will explore the backend implementation using Gin and Go, detailing how the WebSocket server handles connections and messages.&lt;/p&gt;




&lt;h3&gt;
  
  
  Links to the Series
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websockets-3fhb"&gt;Real-Time Web Application Demo with WebSocket - Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websocket-backend-1a1n"&gt;Real-Time Web Application Demo with WebSocket - Backend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>websocket</category>
      <category>nextjs</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Real-Time Web Application demo with WebSocket - Overview</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Thu, 26 Dec 2024 23:51:28 +0000</pubDate>
      <link>https://forem.com/tom-takeru/real-time-web-application-demo-with-websockets-3fhb</link>
      <guid>https://forem.com/tom-takeru/real-time-web-application-demo-with-websockets-3fhb</guid>
      <description>&lt;h3&gt;
  
  
  Introduction to WebSocket
&lt;/h3&gt;

&lt;p&gt;WebSocket has become an essential technology for building real-time, interactive web applications. Unlike HTTP, which relies on a request-response model, WebSocket establishes a persistent, full-duplex communication channel between a client and a server. This capability is particularly useful for applications like chat systems, live notifications, and collaborative tools.&lt;/p&gt;

&lt;p&gt;In this article, I will walk you through a demo application I built to explore WebSocket behavior. The application uses Next.js with TypeScript for the frontend and Gin with Go for the backend. This is the first in a series of articles where we delve into WebSocket basics, implementation details.&lt;/p&gt;




&lt;h3&gt;
  
  
  Demo Application Overview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/tom-takeru/web-socket-demo" rel="noopener noreferrer"&gt;https://github.com/tom-takeru/web-socket-demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo application demonstrates a simple WebSocket-based communication system. It includes the following features:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Real-time updates
&lt;/h4&gt;

&lt;p&gt;Clients can send and receive messages instantly without refreshing the page.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Bidirectional communication
&lt;/h4&gt;

&lt;p&gt;Both the server and the client can initiate communication at any time.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Minimal setup
&lt;/h4&gt;

&lt;p&gt;The application is designed to be lightweight and easy to understand, making it a great starting point for learning WebSocket.&lt;/p&gt;

&lt;p&gt;Frontend:&lt;/p&gt;

&lt;p&gt;Built with Next.js and TypeScript, the client interface is minimal, featuring a text input for messages and a display area for real-time updates.&lt;/p&gt;

&lt;p&gt;Backend:&lt;/p&gt;

&lt;p&gt;Developed using Gin and Go, the server handles WebSocket connections and routes messages between connected clients.&lt;/p&gt;




&lt;h3&gt;
  
  
  Application in Action
&lt;/h3&gt;

&lt;p&gt;Below is a screen capture of the application in action:&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%2Fcqamst7mvuyk90erffaf.gif" 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%2Fcqamst7mvuyk90erffaf.gif" alt="Real time chat application demo gif" width="1523" height="892"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  WebSocket Technical Details
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Understanding WebSocket Protocol
&lt;/h4&gt;

&lt;p&gt;WebSocket is a protocol designed for full-duplex communication over a single TCP connection. It is initiated via an HTTP/HTTPS handshake, after which the connection is upgraded to WebSocket. This allows for efficient real-time communication with reduced overhead compared to traditional HTTP polling or long-polling.&lt;/p&gt;

&lt;h4&gt;
  
  
  Demo Application Workflow
&lt;/h4&gt;

&lt;h5&gt;
  
  
  1. Connection Establishment
&lt;/h5&gt;

&lt;p&gt;The client sends a WebSocket handshake request to the server. The server responds with an acknowledgment, establishing a persistent connection.&lt;/p&gt;

&lt;h5&gt;
  
  
  2. Message Flow
&lt;/h5&gt;

&lt;p&gt;The client can send messages to the server, which then broadcasts them to all connected clients. Similarly, the server can push updates to the clients.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tools and Libraries Used
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Next.js

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Gin

&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;WebSocket enables real-time, interactive experiences that are indispensable for modern web applications. By building this demo application, we gain a deeper understanding of how WebSocket function and how to implement them effectively using Next.js and Gin.&lt;/p&gt;

&lt;p&gt;In the next articles, we will dive deeper into the implementation details.&lt;/p&gt;




&lt;h3&gt;
  
  
  Further Reading and Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websocket-frontend-4kep"&gt;Real-Time Web Application demo with WebSocket - Frontend
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/tom-takeru/real-time-web-application-demo-with-websocket-backend-1a1n"&gt;Real-Time Web Application demo with WebSocket - Backend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>websocket</category>
      <category>typescript</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The simplest demo on SSE(Server-Send Events)</title>
      <dc:creator>tom-takeru</dc:creator>
      <pubDate>Fri, 22 Nov 2024 22:00:21 +0000</pubDate>
      <link>https://forem.com/tom-takeru/the-simplest-demo-on-sseserver-send-events-1mib</link>
      <guid>https://forem.com/tom-takeru/the-simplest-demo-on-sseserver-send-events-1mib</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Server-Sent Events (SSE) is a web technology that allows a server to push real-time updates to a client over HTTP. Unlike WebSockets, SSE is simpler to implement as it uses a one-way communication channel from the server to the browser and works over a regular HTTP connection. It is especially useful for applications requiring periodic updates, such as live scores, notifications, or real-time monitoring dashboards.&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%2F4c9y5m7eygvdokok1gxk.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%2F4c9y5m7eygvdokok1gxk.png" alt="Image description" width="516" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created this demo because I’m currently developing an application where AIs discuss various topics. I wanted to implement some stream-like features and discovered the technology called "SSE".&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo Overview
&lt;/h3&gt;

&lt;p&gt;This demo showcases how Server-Sent Events (SSE) can be used to send real-time updates from an API to the browser. In this example, the browser displays a series of sample messages sent by the server. The simplicity of this demo makes it an excellent starting point for understanding how SSE works and integrating it into your projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo Video
&lt;/h3&gt;

&lt;p&gt;Below is a video demonstrating how the Server-Sent Events (SSE) demo works in real time. Watching this video will give you a better understanding of how the client and server interact to provide real-time updates.&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%2Frarka7ql6ty08xfc01ay.gif" 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%2Frarka7ql6ty08xfc01ay.gif" alt="Demo Video" width="1465" height="867"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;The core implementation of the Server-Sent Events (SSE) demo is split into two parts: the frontend and the backend. The full source code is available on GitHub: &lt;a href="https://github.com/tom-takeru/sse-demo" rel="noopener noreferrer"&gt;sse-demo repository&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Frontend (&lt;code&gt;ui/src/app/page.tsx&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The frontend is built with React and provides buttons to start and stop the SSE connection, displaying real-time messages from the server. Here are the main highlights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connecting to SSE&lt;/strong&gt;: The &lt;code&gt;handleStartConnection&lt;/code&gt; function creates an &lt;code&gt;EventSource&lt;/code&gt; object connected to the &lt;code&gt;/events&lt;/code&gt; endpoint. It listens for messages, open events, and error events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;onmessage&lt;/code&gt;: Handles incoming messages and updates the &lt;code&gt;messages&lt;/code&gt; state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onopen&lt;/code&gt;: Logs when the connection is established.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onerror&lt;/code&gt;: Handles errors, logging details and closing the connection if needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stopping the Connection&lt;/strong&gt;: The &lt;code&gt;handleStopConnection&lt;/code&gt; function closes the SSE connection and cleans up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UI&lt;/strong&gt;: The page includes a simple interface with start/stop buttons and a list to display messages.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&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;useState&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;react&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;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEventSource&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EventSource&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleStartConnection&lt;/span&gt; &lt;span class="o"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newEventSource&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;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080/events&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;handleOnMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MessageEvent&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;onmessage&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;setMessages&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&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;prev&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;data&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;handleOnOpen&lt;/span&gt; &lt;span class="o"&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;Connection established&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;handleOnError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;onerror&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;readyState:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newEventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&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;Connection error occurred.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;newEventSource&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="nf"&gt;setEventSource&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="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleOnClose&lt;/span&gt; &lt;span class="o"&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;Connection is being closed by the server.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;newEventSource&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="nf"&gt;setEventSource&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="p"&gt;};&lt;/span&gt;

    &lt;span class="nx"&gt;newEventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleOnMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;newEventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleOnOpen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;newEventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;handleOnError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;newEventSource&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;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleOnClose&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;setEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newEventSource&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;handleStopConnection&lt;/span&gt; &lt;span class="o"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;eventSource&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="nf"&gt;setEventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;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;Connection closed&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Server-Sent Events Demo&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleStartConnection&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Start Connection
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
        &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleStopConnection&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50 ml-2"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        Stop Connection
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&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;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Backend (&lt;code&gt;api/main.go&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The backend is built using the Gin framework for Go and includes the following key features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CORS Configuration&lt;/strong&gt;: The backend uses the Gin CORS middleware to allow connections during debugging.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSE Endpoint&lt;/strong&gt;: The &lt;code&gt;/events&lt;/code&gt; endpoint streams a series of pre-defined messages to the client with a delay between each message. Key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headers are set to specify the &lt;code&gt;text/event-stream&lt;/code&gt; content type.&lt;/li&gt;
&lt;li&gt;Messages are sent in a loop, with a 2-second delay between each message.&lt;/li&gt;
&lt;li&gt;A final &lt;code&gt;close&lt;/code&gt; event signals the end of the connection.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/gin-contrib/cors"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/gin-gonic/gin"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AllowOrigins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;AllowMethods&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DELETE"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;AllowHeaders&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Origin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;ExposeHeaders&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Content-Length"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;AllowCredentials&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"text/event-stream"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cache-Control"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no-cache"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Connection"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"retry: 0&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&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="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"(Mike) Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Tom) Hi!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Mike) How are you?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Tom) I'm good, thanks! How about you?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Mike) I'm doing well, thanks for asking."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Tom) What's your plan for today?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Mike) I have a meeting in the afternoon."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Tom) Sounds good. Let's catch up later."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Mike) Sure, see you later!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"(Tom) Bye!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// NOTE: This is just for demonstration purposes.&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data: %s&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event: close&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Writer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0:8080"&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;h3&gt;
  
  
  How to Run It
&lt;/h3&gt;

&lt;p&gt;To run this demo, please refer to the &lt;a href="https://github.com/tom-takeru/sse-demo" rel="noopener noreferrer"&gt;README.md&lt;/a&gt; file in the GitHub repository. It contains step-by-step instructions for setting up and running both the frontend and backend of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This demo provides a simple yet effective introduction to Server-Sent Events (SSE), demonstrating how to stream real-time messages from a server to a browser. By focusing on the basics, it’s designed to help you quickly understand the core concepts and start experimenting with SSE in your own projects.&lt;/p&gt;

&lt;p&gt;If you’re interested in trying it out or building upon this example, check out the full source code on GitHub: &lt;a href="https://github.com/tom-takeru/sse-demo" rel="noopener noreferrer"&gt;sse-demo repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>serversentevents</category>
      <category>typescript</category>
      <category>go</category>
      <category>react</category>
    </item>
  </channel>
</rss>
