<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:webfeeds="http://webfeeds.org/rss/1.0" version="2.0">
  <channel>
    <title>mmyoji's blog</title>
    <link>https://blog.mmyoji.com/</link>
    <atom:link href="https://blog.mmyoji.com/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sun, 29 Mar 2026 13:30:05 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume 3.2.2</generator>
    <item>
      <title>Migrate from Jest to node:test in CommonJS</title>
      <link>https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/</guid>
      <content:encoded>
        <![CDATA[<!--more-->
<p>Though this post is a bit minor content, please refer this when you need to
migrate from <a href="https://jestjs.io/">Jest</a> to Node.js built-in test runner
(<code>node:test</code>).</p>
<p>Use Node.js Type Stripping or Deno when you start a new project or your current
project doesn't have large codebase.</p>
<p>I've decided to use <a href="https://www.npmjs.com/package/tsx">tsx</a> (not Type
Stripping) for transpiler because we need to migrate <strong>regular</strong> TypeScript
source code to ESModule first and Type Stripping doesn't support <code>*.tsx</code>.</p>
<h2 id="environment" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#environment" class="header-anchor">Environment</a></h2>
<ul>
<li>Node.js v22.x
<ul>
<li><code>&quot;type&quot;: &quot;commonjs&quot;</code></li>
</ul>
</li>
<li>tsx@4</li>
<li>typescript@5</li>
</ul>
<p><code>tsconfig.json</code> example (Some options are not important.)</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;lib&quot;: [&quot;ES2023&quot;],
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;erasableSyntaxOnly&quot;: true,
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;module&quot;: &quot;Node18&quot;,
    &quot;moduleResolution&quot;: &quot;Node16&quot;,
    &quot;target&quot;: &quot;ES2022&quot;
  },
  &quot;include&quot;: [&quot;src/**/*&quot;]
}
</code></pre>
<h2 id="steps" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#steps" class="header-anchor">Steps</a></h2>
<ol>
<li>Use <a href="https://www.npmjs.com/package/@jest/globals"><code>@jest/globals</code></a> instead of
<code>@types/jest</code> if you use global Jest APIs
(<a href="https://github.com/jest-community/eslint-plugin-jest/blob/v28.11.0/docs/rules/prefer-importing-jest-globals.md"><code>prefer-importing-jest-globals</code></a>
of <a href="https://www.npmjs.com/package/eslint-plugin-jest">eslint-plugin-jest</a>
would be helpful.)</li>
<li>Use <a href="https://nodejs.org/docs/latest-v22.x/api/assert.html"><code>node:assert</code></a>
instead of Jest <code>expect</code> API</li>
<li>Use <a href="https://www.npmjs.com/package/sinon">sinon</a> or other mocking libraries
instead of Jest mocking APIs (e.g. <code>jest.mock()</code>, <code>jest.spyOn()</code>)</li>
<li>Migrate <code>{it,test}.each()</code> w/ regular JS loop</li>
</ol>
<p>After all of them are applied, you would run test via <code>node --test</code> instead of
<code>jest</code>.</p>
<h3 id="expect--%3E-node%3Aassert" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#expect--%3E-node%3Aassert" class="header-anchor"><code>expect</code> -&gt; <code>node:assert</code></a></h3>
<pre><code class="language-ts">// before

import { describe, expect, it } from &quot;@jest/globals&quot;;

describe(&quot;foo()&quot;, () =&gt; {
  it(&quot;returns 'foo'&quot;, () =&gt; {
    expect(foo()).toEqual(&quot;foo&quot;);
  });
});

// after

import { describe, it } from &quot;@jest/globals&quot;;
import assert from &quot;node:assert/strict&quot;;

describe(&quot;foo()&quot;, () =&gt; {
  it(&quot;returns 'foo'&quot;, () =&gt; {
    assert.equal(foo(), &quot;foo&quot;);
  });
});
</code></pre>
<h3 id="jest.mock()--%3E-sinon" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#jest.mock()--%3E-sinon" class="header-anchor"><code>jest.mock()</code> -&gt; <code>sinon</code></a></h3>
<p>If your stub, spy, or mock target is not <strong>an object</strong>, change source code first
because <code>tsx</code> (<code>esbuild</code>) can't handle it.
<a href="https://github.com/sinonjs/sinon/issues/2528#issuecomment-1657185418">see</a></p>
<h4 id="before" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#before" class="header-anchor">Before</a></h4>
<pre><code class="language-ts">// src/bar.ts

export function bar(): string {
  return &quot;bar&quot;;
}

// src/foo.ts

import { bar } from &quot;./bar&quot;;

export function fooBar(): string {
  return `foo${bar()}`;
}

// src/foo.test.ts

import { fooBar } from &quot;./foo&quot;;

import * as barMod from &quot;./bar&quot;;

import { afterEach, beforeEach, describe, it } from &quot;@jest/globals&quot;;
import assert from &quot;node:assert/strict&quot;;
import sinon from &quot;sinon&quot;;

describe(&quot;fooBar()&quot;, () =&gt; {
  let barStub: sinon.SinonStub;

  beforeEach(() =&gt; {
    barStub = sinon.stub(barMod, &quot;bar&quot;);
  });

  afterEach(() =&gt; {
    barStub.restore();
  });

  it(&quot;returns 'fooXXX'&quot;, () =&gt; {
    // This fails
    barStub.returns(&quot;XXX&quot;);

    assert.equal(fooBar(), &quot;fooXXX&quot;);
  });
});
</code></pre>
<h4 id="after" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#after" class="header-anchor">After</a></h4>
<pre><code class="language-ts">// src/bar.ts

export const barMod = {
  bar(): string {
    return &quot;bar&quot;;
  },
};

// src/foo.ts

import { barMod } from &quot;./bar&quot;;

export function fooBar(): string {
  return `foo${barMod.bar()}`;
}

// src/foo.test.ts

import { foo } from &quot;./foo&quot;;

// This works
import { barMod } from &quot;./bar&quot;;

// ...
</code></pre>
<h3 id="it.each--%3E-regular-js-loop" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#it.each--%3E-regular-js-loop" class="header-anchor"><code>it.each</code> -&gt; regular JS loop</a></h3>
<pre><code class="language-ts">// before
it.each([
  { input: {}, expected: { page: 1, limit: 20 } },
  { input: { name: &quot;foo&quot; }, expected: { name: &quot;foo&quot;, page: 1, limit: 20 } },
  {
    input: { name: &quot;&quot;, page: &quot;2&quot;, limit: &quot;50&quot; },
    expected: { page: 2, limit: 50 },
  },
])(&quot;returns $expected w/ $input&quot;, ({ input, expected }) =&gt; {
  assert.deepEqual(validate(input), expected);
});

// after
[
  { input: {}, expected: { page: 1, limit: 20 } },
  { input: { name: &quot;foo&quot; }, expected: { name: &quot;foo&quot;, page: 1, limit: 20 } },
  {
    input: { name: &quot;&quot;, page: &quot;2&quot;, limit: &quot;50&quot; },
    expected: { page: 2, limit: 50 },
  },
].forEach(({ input, expected }) =&gt; {
  it(`returns ${JSON.stringify(expected)} w/ ${JSON.stringify(input)}`, () =&gt; {
    assert.deepEqual(validate(input), expected);
  });
});
</code></pre>
<h2 id="command" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#command" class="header-anchor">Command</a></h2>
<p>The test command would be like this:</p>
<pre><code class="language-sh">$ node --import=tsx --test '**/*.test.*'
</code></pre>
<h2 id="node.js-test-runner-mocks" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/#node.js-test-runner-mocks" class="header-anchor">Node.js test runner mocks</a></h2>
<p>After the migration, you can use Node.js test runner's mocking APIs.</p>
<p>But you need a bit hard work for Module Mocking. (and you need
<code>--experimental-test-module-mocks</code> flag)</p>
<p>The former example would be like the following:</p>
<p><code>src/foo/foo.test.ts</code></p>
<pre><code class="language-ts">// no mocks are used in this file.

import { foo } from &quot;./foo&quot;;

import assert from &quot;node:assert/strict&quot;;
import { describe, it } from &quot;node:test&quot;;

describe(&quot;foo()&quot;, () =&gt; {
  it(&quot;returns 'foo'&quot;, () =&gt; {
    assert.equal(foo(), &quot;foo&quot;);
  });
});
</code></pre>
<p><code>src/foo/foo-bar.test.ts</code></p>
<pre><code class="language-ts">// mocks are used in this file.

import assert from &quot;node:assert/strict&quot;;
import { beforeEach, describe, it, mock } from &quot;node:test&quot;;

describe(&quot;fooBar()&quot;, () =&gt; {
  let barMock = mock.fn&lt;() =&gt; string&gt;();
  let fooBar: () =&gt; string;

  beforeEach(async () =&gt; {
    // This can stub function
    mock.module(&quot;./bar&quot;, {
      namedExports: { bar: barMock },
    });

    // Load the target file after mocking
    ({ fooBar } = await import(&quot;./foo.js&quot;));
  });

  it(&quot;returns 'fooXXX'&quot;, () =&gt; {
    barMock.mock.mockImplementation(() =&gt; &quot;XXX&quot;);

    assert.equal(fooBar(), &quot;fooXXX&quot;);
  });
});
</code></pre>
<p>I hope the Module Mocking API will be better in the future.</p>
]]>
      </content:encoded>
      <pubDate>Wed, 02 Apr 2025 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Improve Legacy Node.js App Structure</title>
      <link>https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/</guid>
      <content:encoded>
        <![CDATA[<!--more-->
<p>A main project I currently tackle on adopts DDD architecture. But nobody in the
project understands it correctly:</p>
<ul>
<li>business logic spreads across everywhere</li>
<li>empty Entities just like DTO</li>
<li>Service classes with no business logic</li>
<li>etc.</li>
</ul>
<p>Due to this situation, I'm now trying to improve application structure. Of
cource, it's the best to use a framework that has strict rules about app/dir
structure. The project is a legacy, express.js based one, tho.</p>
<p>One important thing is <strong>reducing roles (layers)</strong>. There're many layers and
objects in the current architecture. Everyone confuses what/where things should
be put. And I realized it makes sense that Ruby on Rails adopts MVC patterns.</p>
<h2 id="structure" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/#structure" class="header-anchor">Structure</a></h2>
<p>One of the ideas will be based on NestJS approach (see
<a href="https://docs.nestjs.com/first-steps">the tutorial</a>).</p>
<p>Web applications are normally the following 4 steps:</p>
<ol>
<li>receives a request</li>
<li>validates it</li>
<li>applies business logic</li>
<li>returns a response.</li>
</ol>
<p>To acheive this, app structure can be like this:</p>
<pre><code class="language-sh">app/
  posts/
    posts.router.ts
    posts.handlers.ts
    posts.validator.ts
    posts.service.ts
</code></pre>
<ul>
<li><code>posts.router.ts</code> handles <code>/posts/*</code> requests.</li>
<li><code>posts.service.ts</code> hanldes a part of <code>3.</code></li>
</ul>
<pre><code class="language-ts">// posts.router.ts
export const postsRouter = defineRouter(&quot;/posts&quot;, (r) =&gt; {
  // GET /posts
  r.get(&quot;/&quot;, indexHandler);

  // GET /posts/:id
  r.get(&quot;/:id&quot;, showHandler);

  // GET /posts/new
  r.get(&quot;/new&quot;, newHandler);

  // POST /posts
  r.post(&quot;/&quot;, createHandler);

  // GET /posts/:id/edit
  r.get(&quot;/:id/edit&quot;, editHandler);

  // PATCH /posts/:id
  r.patch(&quot;/:id&quot;, updateHandler);

  // DELETE /posts/:id
  r.delete(&quot;/:id&quot;, destroyHandler);
});

// posts.handlers.ts
export const indexHandler = defineHandler(async (req, res) =&gt; {
  const options = validateSearchOptions(req.query);
  const { posts, total, cursor } = await service.fetchList(options);
  res.json({ data: { posts }, total, cursor });
});

export const createHandler = defineHandler(async (req, res) =&gt; {
  const data = validatePost(req.body);
  const post = await service.create(data);
  res.status(201).json({ data: { post } });
});
</code></pre>
<p>You can also arrange the structure like this:</p>
<pre><code class="language-sh">app/
  posts/
    posts.router.ts
    posts.service.ts
    _index/
      index.handler.ts
      index.validator.ts
    _create/
      create.handler.ts
      create.validator.ts
</code></pre>
<p>it can be like the following for more realistic monolith apps:</p>
<pre><code class="language-sh">apps/
  admin/   # admin.example.com/
    posts/
      posts.router.ts
      # ...
  web/     # www.example.com/
    posts/
      posts.router.ts
      # ...
</code></pre>
<p>Other <em>common</em> layers like persistent layer, middlewares, logger, etc. is like
this:</p>
<pre><code class="language-sh">app/
  # ...
lib/
  entities/
    post.ts
    user.ts
  middlewares/
    not-found.middleware.ts
    server-error.middleware.ts
  repositories/
    post.repository.ts
    user.repository.ts
  logger.ts
  define-handler.ts
  define-router.ts
</code></pre>
<h2 id="activerecord-pattern-vs-repository-pattern" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/#activerecord-pattern-vs-repository-pattern" class="header-anchor">ActiveRecord pattern vs Repository pattern</a></h2>
<p>I'd thought Repository pattern would be better than ActiveRecord pattern for
several years. But I have to consider using AR if project members aren't skilled
enough.</p>
<h2 id="final-comment" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/#final-comment" class="header-anchor">Final Comment</a></h2>
<p>I'm just trying this now and don't know what I feel in future.</p>
]]>
      </content:encoded>
      <pubDate>Sat, 01 Jul 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Apply ESLint rules gradually to your legacy project</title>
      <link>https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/</guid>
      <content:encoded>
        <![CDATA[<!--more-->
<p>It's not hard at all to introduce <a href="https://eslint.org/">ESLint</a> to a new
project, but so hard for a legacy project.</p>
<p>Although there are several ways to do this, I'll leave a better way for myself.</p>
<h2 id="steps" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/#steps" class="header-anchor">Steps</a></h2>
<ol>
<li>Upgrade eslint version as far as possible</li>
<li>List up <code>&quot;rules&quot;</code> w/ <code>&quot;error&quot;</code></li>
<li>Add TODO list in <code>&quot;overrides&quot;</code> per directory w/ <code>&quot;warn&quot;</code></li>
<li>Run with
<a href="https://eslint.org/docs/latest/use/command-line-interface#--max-warnings"><code>--max-warnings=0</code></a>
option to newly added files on CI</li>
</ol>
<h2 id="1.-upgrade-eslint-version-as-far-as-possible" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/#1.-upgrade-eslint-version-as-far-as-possible" class="header-anchor">1. Upgrade eslint version as far as possible</a></h2>
<p>If eslint libs are installed in your project, you should upgrade them as far as
possible.</p>
<p>The rules may behave differently and have better options after the upgrade.</p>
<h2 id="2.-list-up-%22rules%22-w%2F-%22error%22" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/#2.-list-up-%22rules%22-w%2F-%22error%22" class="header-anchor">2. List up <code>&quot;rules&quot;</code> w/ <code>&quot;error&quot;</code></a></h2>
<p>After upgrading or installing the eslint libs, you would set up <code>.eslintrc.js</code>.</p>
<p>By the way, I'm explaining this with the old eslint config file in this post.
But the technique itself is also applicable to new config file.</p>
<pre><code class="language-js">// .eslintrc.js

module.exports = {
  // ...

  rules: {
    &quot;@typescript-eslint/no-implicit-any&quot;: [&quot;error&quot;],
  },
  // ...
};
</code></pre>
<p>In this phase, your project may not have passed <code>eslint</code> on CI, but it's OK for
now.</p>
<h2 id="3.-add-todo-list-in-%22overrides%22-per-directory-w%2F-%22warn%22" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/#3.-add-todo-list-in-%22overrides%22-per-directory-w%2F-%22warn%22" class="header-anchor">3. Add TODO list in <code>&quot;overrides&quot;</code> per directory w/ <code>&quot;warn&quot;</code></a></h2>
<p>To mitigate the violations, you can list up the directory list with <code>&quot;warn&quot;</code>.</p>
<p>Suppose your project is built using the MVC style.</p>
<pre><code class="language-js">// .eslintrc.js

module.exports = {
  // ...

  overrides: [
    // TODO: remove listed dir after fixing the violations.
    {
      files: [
        &quot;src/controllers/admin/**/*.ts&quot;,
        &quot;src/controllers/web/**/*.ts&quot;,
        &quot;src/models/**/*.ts&quot;,
        &quot;src/lib/**/*.ts&quot;,
      ],
      rules: {
        &quot;@typescript-eslint/no-implicit-any&quot;: [&quot;warn&quot;],
      },
    },
  ],
};
</code></pre>
<p>For example,</p>
<ol>
<li>fix <code>no-implicit-any</code> violations under the <code>src/controlles/admin/*</code></li>
<li>remove the line <code>&quot;src/controllers/admin/**/*.ts&quot;</code></li>
<li>violations can be detected under the directory</li>
</ol>
<h2 id="4.-run-with---max-warnings%3D0-option-to-newly-added-files-on-ci" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/#4.-run-with---max-warnings%3D0-option-to-newly-added-files-on-ci" class="header-anchor">4. Run with <code>--max-warnings=0</code> option to newly added files on CI</a></h2>
<p>This is an optional, but very effective.</p>
<p>You can apply stricter rules to new files. This can be done with the following
command:</p>
<pre><code class="language-shell"># `remotes/origin/main` can be replaced with your base branch.
$ eslint \
    --max-warnings=0 \
    $(git diff --name-only --diff-filter=A remotes/origin/main HEAD)
</code></pre>
<p>ref:
<a href="https://stackoverflow.com/a/15535048">https://stackoverflow.com/a/15535048</a></p>
<p>If the new files are violated against any <code>&quot;warn&quot;</code> rules, they are detected as
<code>&quot;error&quot;</code>.</p>
<p>You can avoid violated files are added in your project.</p>
]]>
      </content:encoded>
      <pubDate>Thu, 09 Mar 2023 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Migrate to lume</title>
      <link>https://blog.mmyoji.com/posts/2022/09-21-migrate-to-lume/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/09-21-migrate-to-lume/</guid>
      <content:encoded>
        <![CDATA[<p>I've migrated the static site generator for this blog from
<a href="https://gohugo.io/">hugo</a> to <a href="https://lume.land/">lume</a>.</p>
<!--more-->
<p>I had been looking for an alternative static site generator built in Deno and
<a href="https://github.com/denoland/deno_blog">deno_blog</a> was a candidate. But it
currently doesn't support pagination
(<a href="https://github.com/denoland/deno_blog/pull/73">p-r exists</a> tho).</p>
<p>lume is almost the same as hugo, at least for me, it's enough. First I thought I
need to design entire site by myself, but I found
<a href="https://github.com/lumeland/theme-simple-blog">theme-simple-blog</a> can be used
as a plugin (like hugo theme).</p>
<p>Then I tried to migrate my blog to lume and I could. It just took less than an
hour.</p>
<p>The steps was like following:</p>
<ol>
<li>Run init command (<code>deno run -A https://deno.land/x/lume/init.ts</code>) under the
root of the blog repo.</li>
<li>Remove extra <code>content</code> directory for the posts</li>
<li>Remove unnecessary hugo files</li>
<li>Add <code>_data.yml</code></li>
<li>Add my own <code>about.md</code></li>
<li>Update <code>.gitignore</code></li>
<li>Add <code>them-simple-blog</code> plugin</li>
</ol>
<p>I've also migrated the hosting from <a href="https://netlify.com/">Netlfiy</a> to
<a href="https://deno.com/deploy">Deno Deploy</a>.</p>
<p>See the setup detail
<a href="https://lume.land/docs/advanced/deployment/#deno-deploy">here</a>.</p>
<p>I am now satified with it :)</p>
]]>
      </content:encoded>
      <pubDate>Wed, 21 Sep 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>How to enable redirect in next-fetch</title>
      <link>https://blog.mmyoji.com/posts/2022/09-21-next-fetch-redirect/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/09-21-next-fetch-redirect/</guid>
      <content:encoded>
        <![CDATA[<!--more-->
<p><a href="https://github.com/vercel-labs/next-fetch">next-fetch</a> is useful when you use
both SWR and Next.js API Routes.</p>
<p>But currently the documentation is poor. I am a bit struggled with how to use
<code>hookResponse</code> option in query or mutation.</p>
<h2 id="usage" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/09-21-next-fetch-redirect/#usage" class="header-anchor">Usage</a></h2>
<p>⚠️ This is just for <code>@next-fetch/swr@0.0.2</code> and could be changed in future
versions.</p>
<p>For API routes, see
<a href="https://next-fetch-pi.vercel.app/swr#add-a-mutation-to-your-api-endpoint">the doc</a>.</p>
<pre><code class="language-tsx">// pages/form.tsx

export default function MyFormPage() {
  return (
    &lt;form action=&quot;/api/simple?__handler=useMutation&quot; method=&quot;POST&quot;&gt;
      &lt;input type=&quot;text&quot; name=&quot;name&quot; /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  );
}
</code></pre>
]]>
      </content:encoded>
      <pubDate>Wed, 21 Sep 2022 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Next.js Edge API Routes</title>
      <link>https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/</guid>
      <content:encoded>
        <![CDATA[<!--more-->
<h2 id="conclusion" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/#conclusion" class="header-anchor">Conclusion</a></h2>
<p><code>runtime: &quot;experimental-edge&quot;</code> behaves differently from <code>runtime: &quot;nodejs&quot;</code>.</p>
<p>It returns different HTTP response headers and I will explain the detail in this
post (although this might be wrong partially.)</p>
<h2 id="my-concern" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/#my-concern" class="header-anchor">My Concern</a></h2>
<p><a href="https://nextjs.org/blog/next-12-2">Next.js v12.2</a> introduces
<a href="https://nextjs.org/docs/api-reference/edge-runtime">Edge Runtime</a>.</p>
<p>I'd just understood it couldn't use Node.js standard APIs and was lightweight.</p>
<p>But I was not sure how this worked and how differed from <code>runtime: &quot;nodejs&quot;</code> and
wondered whether this affected when choosing self-hosted.</p>
<p>The official document says like this:</p>
<blockquote>
<p>Edge API Routes can stream responses from the server and run after cached
files (e.g. HTML, CSS, JavaScript) have been accessed.</p>
</blockquote>
<p>...OK, I will test the new API routes!</p>
<h2 id="test-code" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/#test-code" class="header-anchor">Test Code</a></h2>
<p>Set <code>experimental.runtime: &quot;nodejs&quot;</code> in <code>next.config.js</code></p>
<p><code>pages/api/test.ts</code> uses <code>runtime: &quot;nodejs&quot;</code>:</p>
<pre><code class="language-ts">import { NextApiHandler } from &quot;next&quot;;

const handler: NextApiHandler = async (_req, res) =&gt; {
  res.status(200).json({ name: &quot;Jim Halpert&quot; });
};

export default handler;
</code></pre>
<p><code>pages/api/test-edge.ts</code> uses <code>runtime: &quot;experimental-edge&quot;</code>:</p>
<pre><code class="language-ts">import { NextApiHandler } from &quot;next&quot;;

export const config = {
  runtime: &quot;experimental-edge&quot;,
};

const handler: NextApiHandler = async (_req, res) =&gt; {
  // see: https://nextjs.org/docs/api-routes/edge-api-routes#json-response
  return new Response(
    JSON.stringify({
      name: &quot;Jim Halpert&quot;,
    }),
    {
      status: 200,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json; charset=utf-8&quot;,
      },
    },
  );
};

export default handler;
</code></pre>
<p>Both of them returns the same response body.</p>
<h2 id="result" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/#result" class="header-anchor">Result</a></h2>
<p>I picked up only response headers part.</p>
<p><code>curl -v http://localhost:xxx/api/test</code></p>
<pre><code class="language-txt">&lt; HTTP/1.1 200 OK
&lt; Content-Type: application/json; charset=utf-8
&lt; ETag: &quot;16-DlZOPLD8Q/zEnYFRYsdmnqlUPWI&quot;
&lt; Content-Length: 22
&lt; Vary: Accept-Encoding
&lt; Date: Thu, 30 Jun 2022 11:42:50 GMT
&lt; Connection: keep-alive
&lt; Keep-Alive: timeout=5
</code></pre>
<p><code>curl -v http://localhost:xxx/api/test-edge</code></p>
<pre><code class="language-txt">&lt; HTTP/1.1 200 OK
&lt; Content-Type: application/json; charset=utf-8
&lt; Date: Thu, 30 Jun 2022 11:43:39 GMT
&lt; Connection: keep-alive
&lt; Keep-Alive: timeout=5
&lt; Transfer-Encoding: chunked
</code></pre>
<p><code>experimental-edge</code> runtime:</p>
<ul>
<li>lacks <code>ETag</code>, <code>Content-Length</code>, and <code>Vary</code> headers</li>
<li>adds <code>Tranfer-Encoding: chunked</code> header</li>
</ul>
<p><code>Transfer-Encoding: chunked</code> sends HTTP repsonse as <code>chunk</code> (or stream)
according to
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding">MDN doc</a>.</p>
<ul>
<li>With this header, <code>Content-Length</code> is omitted from the doc.</li>
<li>It's understandable <code>Vary: Accepct-Encoding</code> is omitted for this endpoint
because <code>Transfer-Encoding</code> is specified (chunks are always sent).</li>
<li>The reason <code>ETag</code> is dropped, I guess, might be that the endpoint could not
end in a single request.</li>
</ul>
<p>Again, my understanding for HTTP is not enough and this post might be wrong.
Please check correct sources by yourself.</p>
<h2 id="references" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/#references" class="header-anchor">References</a></h2>
<ul>
<li><a href="https://nextjs.org/blog/next-12-2">Blog - Next.js 12.2 | Next.js</a></li>
<li><a href="https://nextjs.org/docs/api-reference/edge-runtime">Edge Runtime | Next.js</a></li>
<li><a href="https://nextjs.org/docs/api-routes/edge-api-routes#json-response">API Routes: Edge API Routes (Beta) | Next.js</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag">ETag - HTTP | MDN</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary">Vary - HTTP | MDN</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding">Transfer-Encoding - HTTP | MDN</a></li>
</ul>
]]>
      </content:encoded>
      <pubDate>Thu, 30 Jun 2022 11:21:04 GMT</pubDate>
    </item>
    <item>
      <title>Activate WSLg in Pengwin</title>
      <link>https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/</guid>
      <content:encoded>
        <![CDATA[<p>I tried <a href="https://github.com/microsoft/wslg">WSLg</a> because I might need to run
<code>chromedriver</code> for next job.</p>
<p>I had ever struggled and gave up running <code>chromedriver</code> in normal WSL(WSL2)
environment before, so I thought this was a good opportunity.</p>
<p>The following step is limited for WSL2 +
<a href="https://github.com/WhitewaterFoundry/Pengwin">Pengwin</a> distro that I've been
using.</p>
<!--more-->
<h2 id="environment" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/#environment" class="header-anchor">Environment</a></h2>
<ul>
<li>OS: Windows 11, 21H2, 22000.708</li>
<li>GPU: GeForce RTX 2080</li>
</ul>
<h2 id="steps" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/#steps" class="header-anchor">Steps</a></h2>
<p>ref:
<a href="https://www.whitewaterfoundry.com/blog/2021/4/21/gui-app-support-has-arrived-for-the-windows-subsystem-for-linux-on-the-latest-windows-insiders-preview-build-21362-and-pengwin-supports-it-out-of-the-box">GUI app support has arrived for the Windows Subsystem for Linux on the latest Windows Insiders Preview build 21362+ and Pengwin supports it out of the box — Whitewater Foundry</a></p>
<ol>
<li>Install <a href="https://developer.nvidia.com/cuda/wsl">NVIDIA GPU driver for WSL</a></li>
<li>Install required packages in WSL</li>
</ol>
<pre><code class="language-sh">pengwin-setup update

# Open setup dialog
# and select [GUI], then install necessary packages.
pengwin-setup
</code></pre>
<p>After package installation, restart WSL (or restart Windows).</p>
<h2 id="install-google-chrome" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/#install-google-chrome" class="header-anchor">Install Google Chrome</a></h2>
<p>ref: https://github.com/microsoft/wslg#install-and-run-gui-apps</p>
<pre><code class="language-sh">cd /tmp
sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
sudo apt install --fix-broken -y
sudo dpkg -i google-chrome-stable_current_amd64.deb

# Start Google Chrome app in Linux GUI for test
google-chrome
</code></pre>
<h2 id="install-chromedriver" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/#install-chromedriver" class="header-anchor">Install chromedriver</a></h2>
<p>ref: https://tecadmin.net/setup-selenium-with-chromedriver-on-debian/</p>
<pre><code class="language-sh"># 1. check your google chrome version on WSLg
# 2. Download the same version of chromedriver with google-chrome
#    http://chromedriver.storage.googleapis.com/index.html
#    The version was 102.0.5005.61 this time.

cd /tmp
wget http://chromedriver.storage.googleapis.com/102.0.5005.61/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo mv chromedriver /usr/bin/chromedriver
sudo chown root:root /usr/bin/chromedriver
sudo chmod +x /usr/bin/chromedriver
</code></pre>
<p>Run the following script when you want to test.</p>
<p>ref: https://www.npmjs.com/package/selenium-webdriver</p>
<pre><code class="language-js">/**
 * $ npm init -y
 * $ npm install selenium-webdriver
 * $ vim main.js
 */

// main.js
const { Builder, Browser, By, Key, until } = require(&quot;selenium-webdriver&quot;);

(async function main() {
  const driver = await new Builder().forBrowser(Browser.CHROME).build();
  try {
    await driver.get(&quot;http://www.google.com/ncr&quot;);
    await driver.findElement(By.name(&quot;q&quot;)).sendKeys(&quot;webdriver&quot;, Key.RETURN);
    await driver.wait(until.titleIs(&quot;webdriver - Google Search&quot;), 1000);
    // Replace the string with 'webdriver - Google 検索' when your language setting is Japanese.
  } finally {
    await driver.quit();
  }
})();

// node main.js
</code></pre>
]]>
      </content:encoded>
      <pubDate>Tue, 31 May 2022 00:02:42 GMT</pubDate>
    </item>
    <item>
      <title>Disable all inputs in form</title>
      <link>https://blog.mmyoji.com/posts/2022/05-29-disable-all-inputs-in-form/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/05-29-disable-all-inputs-in-form/</guid>
      <content:encoded>
        <![CDATA[<p>When you want to disable all <code>input</code>s in <code>form</code> after submitting, you can use
<code>fieldset</code> for the purpose.</p>
<!--more-->
<pre><code class="language-html">&lt;form action=&quot;#&quot;&gt;
  &lt;fieldset disabled&gt;
    &lt;!-- like this --&gt;
    &lt;legend&gt;Sign in&lt;/legend&gt;

    &lt;label for=&quot;email&quot;&gt;Email&lt;/label&gt;
    &lt;input id=&quot;email&quot; name=&quot;email&quot; type=&quot;email&quot;&gt;

    &lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;
    &lt;input id=&quot;password&quot; name=&quot;password&quot; type=&quot;password&quot;&gt;

    &lt;input type=&quot;submit&quot;&gt;
    &lt;!-- button is also disabled
      &lt;button&gt;Submit&lt;/button&gt;
    --&gt;
  &lt;/fieldset&gt;
&lt;/form&gt;
</code></pre>
<p>see:
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"><fieldset>: The Field Set element - HTML: HyperText Markup Language | MDN</a></p>
]]>
      </content:encoded>
      <pubDate>Sun, 29 May 2022 00:24:07 GMT</pubDate>
    </item>
    <item>
      <title>Transform Stream in Deno</title>
      <link>https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/</guid>
      <content:encoded>
        <![CDATA[<p>I wrote Node.js version of this: <a href="https://blog.mmyoji.com/posts/2022/01-24-transform-stream/">see</a>.</p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams APIs</a> are
bit different from <code>node:stream</code>.</p>
<!--more-->
<h2 id="code" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/#code" class="header-anchor">Code</a></h2>
<pre><code class="language-typescript">interface Post {
  id: number;
  title: string;
}

const posts: Post[] = [
  { id: 1, title: &quot;a&quot; },
  { id: 2, title: &quot;b&quot; },
  { id: 3, title: &quot;c&quot; },
  { id: 4, title: &quot;d&quot; },
  { id: 5, title: &quot;e&quot; },
  { id: 6, title: &quot;f&quot; },
  { id: 7, title: &quot;g&quot; },
  { id: 8, title: &quot;h&quot; },
  { id: 9, title: &quot;i&quot; },
  { id: 10, title: &quot;j&quot; },
];

// Dummy ORM API
const db = {
  post: {
    findMany({ take, skip }: { take: number; skip: number }): Promise&lt;Post[]&gt; {
      return Promise.resolve(posts.slice(skip, skip + take));
    },
  },
};

function postStream(take: number): ReadableStream&lt;Post&gt; {
  let skip = 0;

  return new ReadableStream&lt;Post&gt;(
    {
      async pull(controller) {
        const posts = await db.post.findMany({
          skip,
          take,
        });
        for (const post of posts) {
          controller.enqueue(post);
        }

        if (posts.length &lt; take) {
          controller.close();
          return;
        }

        skip = skip + take;
      },
    },
    { highWaterMark: take },
  );
}

function toCSV(take: number): TransformStream&lt;Post, string&gt; {
  return new TransformStream&lt;Post, string&gt;(
    {
      start(controller) {
        controller.enqueue(`id,title`);
      },
      transform(chunk, controller) {
        controller.enqueue(`${chunk.id},${chunk.title}`);
      },
      flush(controller) {
        controller.terminate();
      },
    },
    {
      highWaterMark: take,
    },
  );
}

const take = 3;
const stream = postStream(take).pipeThrough(toCSV(take));
for await (const chunk of stream) {
  console.log(chunk);
}
</code></pre>
<h2 id="references" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/#references" class="header-anchor">References</a></h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Streams API - Web APIs | MDN</a></li>
<li><a href="https://web.dev/streams/">Streams—The definitive guide</a></li>
</ul>
]]>
      </content:encoded>
      <pubDate>Thu, 26 May 2022 08:32:22 GMT</pubDate>
    </item>
    <item>
      <title>Handling large JSON file in Deno</title>
      <link>https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/</link>
      <guid isPermaLink="false">https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/</guid>
      <content:encoded>
        <![CDATA[<h2 id="conclusion" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/#conclusion" class="header-anchor">Conclusion</a></h2>
<p>Avoid using large JSON file, but use CSV or other easy-to-parse file format for
stream API.</p>
<p>Please tell me if you have a better solution.</p>
<!--more-->
<h2 id="motivation" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/#motivation" class="header-anchor">Motivation</a></h2>
<p>When you have to handle large file (mainly in server side), you normally use
<a href="https://nodejs.org/api/stream.html">Stream API</a> in Node.js.</p>
<p>In Deno, you don't have it but use
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Streams_API">Web Streams API</a>
instead. (Actually you could use <code>std/node</code> libraries:
<a href="https://deno.land/manual/node/std_node">see</a>)</p>
<p>Stream handles data as <code>chunk</code>s, but JSON file, an object rather than an array
of JSON objects especially, is hard to be handled.</p>
<h2 id="read-file-as-stream-in-deno" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/#read-file-as-stream-in-deno" class="header-anchor">Read file as stream in Deno</a></h2>
<p>This is a sample code to read JSON file as string in Deno.</p>
<pre><code class="language-ts">// tmp/
//   test.ts
//   test.json

import { readableStreamFromReader } from &quot;https://deno.land/std@0.140.0/streams/mod.ts&quot;;

const file = await Deno.open(&quot;./tmp/test.json&quot;, { read: true });

// Uint8Array
const byteStream = readableStreamFromReader(file);

// Uint8Array -&gt; String
const decodedStream = byteStream.pipeThrough(new TextDecoderStream());

for await (const chunk of decodedStream) {
  console.log({ chunk: JSON.parse(chunk) });
}

// $ deno run --allow-read ./tmp/test.ts
</code></pre>
<p>If the file is not so large, the chunk is its entire content as string. But if
the file is large enough, the chunk is not JSON-parsable one.</p>
<h2 id="how-to-parse-json-chunk" tabindex="-1"><a href="https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/#how-to-parse-json-chunk" class="header-anchor">How to parse JSON chunk</a></h2>
<p>There is (was) a library called
<a href="https://www.npmjs.com/package/JSONStream">JSONParse</a> on npm. But it's not
maintained yet and only supports Node.js environment.</p>
<p>So there are several options:</p>
<ol>
<li>Write your own parser: like as
<a href="https://stackoverflow.com/questions/58070346/reading-large-json-file-in-deno">this stackoverflow post</a></li>
<li>Use other file format (like CSV):
<a href="https://c2fo.github.io/fast-csv/">fast-csv</a> (found in
<a href="https://esm.sh/fast-csv">esm.sh</a>) is a good library</li>
<li>Avoid using large JSON</li>
</ol>
<p>I rarely have experienced like this but I've tried thinking about it for
exercise.</p>
]]>
      </content:encoded>
      <pubDate>Tue, 24 May 2022 05:59:21 GMT</pubDate>
    </item>
  </channel>
</rss>