<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike Jung</title>
    <description>The latest articles on DEV Community by Mike Jung (@mikejung).</description>
    <link>https://dev.to/mikejung</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4144857%2Fa9277aa8-66d7-4505-a395-80586c331666.png</url>
      <title>DEV Community: Mike Jung</title>
      <link>https://dev.to/mikejung</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mikejung"/>
    <language>en</language>
    <item>
      <title>I Missed @Service in Node.js, So I Built It with Express</title>
      <dc:creator>Mike Jung</dc:creator>
      <pubDate>Sat, 26 Sep 2026 21:20:45 +0000</pubDate>
      <link>https://dev.to/mikejung/i-missed-service-in-nodejs-so-i-built-it-with-express-1d7m</link>
      <guid>https://dev.to/mikejung/i-missed-service-in-nodejs-so-i-built-it-with-express-1d7m</guid>
      <description>&lt;h2&gt;
  
  
  Let the browser console create the files
&lt;/h2&gt;

&lt;p&gt;At first, the browser console was only a debugging tool.&lt;/p&gt;

&lt;p&gt;I wrote the same create, read, update, and delete code several times. The console already knew about controllers, services, tables, and SQL query names. So I added code generation.&lt;/p&gt;

&lt;p&gt;The basic flow is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQL → New SQL file:&lt;/strong&gt; choose a table and create named queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service → New service:&lt;/strong&gt; connect service methods to the queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controller → New controller:&lt;/strong&gt; choose the service and create the routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before it creates the file, the controller screen shows every route. It also shows the linked service method and SQL query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4i9pylsv96myug499kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4i9pylsv96myug499kz.png" alt="The aidot-express New controller screen showing SnackController, five routes, service methods, SQL queries, and generated code." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The New controller screen shows five routes and how each route connects to a service method and an SQL query.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After I save the controller, the route is ready without restarting the backend.&lt;/p&gt;

&lt;p&gt;The console is optional. I can still create files in an editor and place them in &lt;code&gt;workspace/controller&lt;/code&gt;, &lt;code&gt;workspace/service&lt;/code&gt;, or &lt;code&gt;workspace/sql&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When the server is running, the console finds these files and shows a &lt;strong&gt;Load&lt;/strong&gt; button. This extra click is safer than loading a file while I am still editing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the API right away
&lt;/h2&gt;

&lt;p&gt;After I create a controller, I can call it from the built-in API tester.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6v7fevwdgyi9hpmwuz7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6v7fevwdgyi9hpmwuz7c.png" alt="The aidot-express API tester calling GET /api/snack and showing a 200 OK response with three snack records." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;code&gt;GET /api/snack&lt;/code&gt; returns &lt;code&gt;200 OK&lt;/code&gt; and three test rows without restarting the backend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My work cycle is now short:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL → service → controller → test → edit → test again&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A restart may take only a few seconds, but it can still break my focus. Removing that step makes it easier to try a change and check the result immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  See each step of a request
&lt;/h2&gt;

&lt;p&gt;A single number such as “7 ms” does not tell me what happened inside a request.&lt;/p&gt;

&lt;p&gt;I wanted to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which route received the request,&lt;/li&gt;
&lt;li&gt;which controller and service ran,&lt;/li&gt;
&lt;li&gt;which SQL query ran,&lt;/li&gt;
&lt;li&gt;how many rows came back,&lt;/li&gt;
&lt;li&gt;and how long each step took.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Request Trace screen shows these steps in order. In this local example, &lt;code&gt;GET /api/snack&lt;/code&gt; finished in &lt;strong&gt;7 ms&lt;/strong&gt;. One SQL query took &lt;strong&gt;1 ms&lt;/strong&gt; and returned three rows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9fhyvgth9v6krxubh41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy9fhyvgth9v6krxubh41.png" alt="The aidot-express Request Trace screen showing GET /api/snack, a 200 response, the controller, SnackService.list, snack:findAll, three rows, and step times." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The trace shows the route, controller, SQL query, service method, row count, status, and time for one request.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The 7 ms result is not a speed test. It is only a small request on a local computer. The important part is the list of steps.&lt;/p&gt;

&lt;p&gt;When a request is slow or fails, I can see where to start looking.&lt;/p&gt;

&lt;p&gt;The console also records request IDs and user activity. In hospital software, it can be important to know who opened data and what happened after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Vue 3 screen
&lt;/h2&gt;

&lt;p&gt;After the backend structure worked, I asked another question. Could the same information also create a useful starting screen?&lt;/p&gt;

&lt;p&gt;The Screen Designer lets me build a basic data screen for creating, viewing, editing, and deleting records. I can preview it in desktop, tablet, and mobile sizes, and then export a Vue 3 project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F87cmg9kfx05fc2jsuc10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F87cmg9kfx05fc2jsuc10.png" alt="The aidot-express Screen Designer showing a My Snack list and controls for desktop, tablet, mobile, and Code export." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Screen Designer previews a snack list and can export normal Vue 3 source code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The exported project is normal source code. A team can open it, change it, and continue development outside the designer. The final application does not have to stay inside aidot-express.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work without internet access
&lt;/h2&gt;

&lt;p&gt;Some hospital networks are isolated or have strict network rules. The project must still work in those environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The user-interface files are stored and served locally. The app does not need a CDN when it runs.&lt;/li&gt;
&lt;li&gt;The framework and browser console can run inside a private network.&lt;/li&gt;
&lt;li&gt;On Windows, the backend can run as a Windows service. An Electron desktop app can connect to it as a viewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The public GitHub repository includes the framework, browser console, API tester, request tracing, and Screen Designer described in this article.&lt;/p&gt;

&lt;p&gt;High availability, backup and restore, column encryption, and hospital-middleware integration are separate Enterprise features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three lessons I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Clear rules save repeated decisions.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A small API does not need every feature of a large framework. However, it still helps to have fixed places for controllers, services, and SQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A code generator should create normal source code.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The files created by the browser console can be opened, reviewed, stored in Git, and edited by hand. Generated files and hand-written files work in the same way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A fast work cycle is more useful than many templates.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Creating five basic routes is helpful. But the bigger benefit is being able to create, load, call, check, and change an endpoint without losing focus.&lt;/p&gt;
&lt;h2&gt;
  
  
  When to use it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;aidot-express can be a good fit for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal REST APIs and admin tools,&lt;/li&gt;
&lt;li&gt;teams that like separate Controller and Service layers,&lt;/li&gt;
&lt;li&gt;projects where developers or database administrators need to read the SQL directly,&lt;/li&gt;
&lt;li&gt;systems that run inside a company or hospital network,&lt;/li&gt;
&lt;li&gt;and small teams that manage the whole application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It is not designed to be:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a platform for managing many microservices,&lt;/li&gt;
&lt;li&gt;a framework that hides all database work behind an ORM,&lt;/li&gt;
&lt;li&gt;or the best choice for every Node.js project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PostgreSQL is not supported yet. The open-source repository is available under the Apache License 2.0.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;For the easiest setup, I recommend Node.js 22 or newer and MariaDB or MySQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mike-jung/aidot-express.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aidot-express
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env      &lt;span class="c"&gt;# add your database settings&lt;/span&gt;
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, use this command instead of &lt;code&gt;cp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Copy-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:7901&lt;/code&gt;. Sign in with the default administrator account shown in the GitHub README, and change the default password right away.&lt;/p&gt;

&lt;p&gt;Then create an SQL file, a service, and a controller. Open &lt;strong&gt;API Test&lt;/strong&gt; and call the new route.&lt;/p&gt;

&lt;p&gt;If you build internal tools with Express, I would like to know how much structure you prefer. When does helpful structure start to feel too heavy?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/mike-jung/aidot-express" rel="noopener noreferrer"&gt;https://github.com/mike-jung/aidot-express&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I am the creator of aidot-express. The screenshots and code examples in this article come from my project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>node</category>
      <category>javascript</category>
      <category>web</category>
    </item>
  </channel>
</rss>
