{"version":"https://jsonfeed.org/version/1","title":"mmyoji's blog","home_page_url":"https://blog.mmyoji.com/","feed_url":"https://blog.mmyoji.com/feed.json","items":[{"id":"https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/","url":"https://blog.mmyoji.com/posts/2025/04-02-jest-to-node-test/","title":"Migrate from Jest to node:test in CommonJS","content_html":"<!--more-->\n<p>Though this post is a bit minor content, please refer this when you need to\nmigrate from <a href=\"https://jestjs.io/\">Jest</a> to Node.js built-in test runner\n(<code>node:test</code>).</p>\n<p>Use Node.js Type Stripping or Deno when you start a new project or your current\nproject doesn't have large codebase.</p>\n<p>I've decided to use <a href=\"https://www.npmjs.com/package/tsx\">tsx</a> (not Type\nStripping) for transpiler because we need to migrate <strong>regular</strong> TypeScript\nsource code to ESModule first and Type Stripping doesn't support <code>*.tsx</code>.</p>\n<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>\n<ul>\n<li>Node.js v22.x\n<ul>\n<li><code>&quot;type&quot;: &quot;commonjs&quot;</code></li>\n</ul>\n</li>\n<li>tsx@4</li>\n<li>typescript@5</li>\n</ul>\n<p><code>tsconfig.json</code> example (Some options are not important.)</p>\n<pre><code class=\"language-json\">{\n  &quot;compilerOptions&quot;: {\n    &quot;lib&quot;: [&quot;ES2023&quot;],\n    &quot;baseUrl&quot;: &quot;.&quot;,\n    &quot;allowSyntheticDefaultImports&quot;: true,\n    &quot;erasableSyntaxOnly&quot;: true,\n    &quot;strict&quot;: true,\n    &quot;esModuleInterop&quot;: true,\n    &quot;module&quot;: &quot;Node18&quot;,\n    &quot;moduleResolution&quot;: &quot;Node16&quot;,\n    &quot;target&quot;: &quot;ES2022&quot;\n  },\n  &quot;include&quot;: [&quot;src/**/*&quot;]\n}\n</code></pre>\n<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>\n<ol>\n<li>Use <a href=\"https://www.npmjs.com/package/@jest/globals\"><code>@jest/globals</code></a> instead of\n<code>@types/jest</code> if you use global Jest APIs\n(<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>\nof <a href=\"https://www.npmjs.com/package/eslint-plugin-jest\">eslint-plugin-jest</a>\nwould be helpful.)</li>\n<li>Use <a href=\"https://nodejs.org/docs/latest-v22.x/api/assert.html\"><code>node:assert</code></a>\ninstead of Jest <code>expect</code> API</li>\n<li>Use <a href=\"https://www.npmjs.com/package/sinon\">sinon</a> or other mocking libraries\ninstead of Jest mocking APIs (e.g. <code>jest.mock()</code>, <code>jest.spyOn()</code>)</li>\n<li>Migrate <code>{it,test}.each()</code> w/ regular JS loop</li>\n</ol>\n<p>After all of them are applied, you would run test via <code>node --test</code> instead of\n<code>jest</code>.</p>\n<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>\n<pre><code class=\"language-ts\">// before\n\nimport { describe, expect, it } from &quot;@jest/globals&quot;;\n\ndescribe(&quot;foo()&quot;, () =&gt; {\n  it(&quot;returns 'foo'&quot;, () =&gt; {\n    expect(foo()).toEqual(&quot;foo&quot;);\n  });\n});\n\n// after\n\nimport { describe, it } from &quot;@jest/globals&quot;;\nimport assert from &quot;node:assert/strict&quot;;\n\ndescribe(&quot;foo()&quot;, () =&gt; {\n  it(&quot;returns 'foo'&quot;, () =&gt; {\n    assert.equal(foo(), &quot;foo&quot;);\n  });\n});\n</code></pre>\n<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>\n<p>If your stub, spy, or mock target is not <strong>an object</strong>, change source code first\nbecause <code>tsx</code> (<code>esbuild</code>) can't handle it.\n<a href=\"https://github.com/sinonjs/sinon/issues/2528#issuecomment-1657185418\">see</a></p>\n<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>\n<pre><code class=\"language-ts\">// src/bar.ts\n\nexport function bar(): string {\n  return &quot;bar&quot;;\n}\n\n// src/foo.ts\n\nimport { bar } from &quot;./bar&quot;;\n\nexport function fooBar(): string {\n  return `foo${bar()}`;\n}\n\n// src/foo.test.ts\n\nimport { fooBar } from &quot;./foo&quot;;\n\nimport * as barMod from &quot;./bar&quot;;\n\nimport { afterEach, beforeEach, describe, it } from &quot;@jest/globals&quot;;\nimport assert from &quot;node:assert/strict&quot;;\nimport sinon from &quot;sinon&quot;;\n\ndescribe(&quot;fooBar()&quot;, () =&gt; {\n  let barStub: sinon.SinonStub;\n\n  beforeEach(() =&gt; {\n    barStub = sinon.stub(barMod, &quot;bar&quot;);\n  });\n\n  afterEach(() =&gt; {\n    barStub.restore();\n  });\n\n  it(&quot;returns 'fooXXX'&quot;, () =&gt; {\n    // This fails\n    barStub.returns(&quot;XXX&quot;);\n\n    assert.equal(fooBar(), &quot;fooXXX&quot;);\n  });\n});\n</code></pre>\n<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>\n<pre><code class=\"language-ts\">// src/bar.ts\n\nexport const barMod = {\n  bar(): string {\n    return &quot;bar&quot;;\n  },\n};\n\n// src/foo.ts\n\nimport { barMod } from &quot;./bar&quot;;\n\nexport function fooBar(): string {\n  return `foo${barMod.bar()}`;\n}\n\n// src/foo.test.ts\n\nimport { foo } from &quot;./foo&quot;;\n\n// This works\nimport { barMod } from &quot;./bar&quot;;\n\n// ...\n</code></pre>\n<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>\n<pre><code class=\"language-ts\">// before\nit.each([\n  { input: {}, expected: { page: 1, limit: 20 } },\n  { input: { name: &quot;foo&quot; }, expected: { name: &quot;foo&quot;, page: 1, limit: 20 } },\n  {\n    input: { name: &quot;&quot;, page: &quot;2&quot;, limit: &quot;50&quot; },\n    expected: { page: 2, limit: 50 },\n  },\n])(&quot;returns $expected w/ $input&quot;, ({ input, expected }) =&gt; {\n  assert.deepEqual(validate(input), expected);\n});\n\n// after\n[\n  { input: {}, expected: { page: 1, limit: 20 } },\n  { input: { name: &quot;foo&quot; }, expected: { name: &quot;foo&quot;, page: 1, limit: 20 } },\n  {\n    input: { name: &quot;&quot;, page: &quot;2&quot;, limit: &quot;50&quot; },\n    expected: { page: 2, limit: 50 },\n  },\n].forEach(({ input, expected }) =&gt; {\n  it(`returns ${JSON.stringify(expected)} w/ ${JSON.stringify(input)}`, () =&gt; {\n    assert.deepEqual(validate(input), expected);\n  });\n});\n</code></pre>\n<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>\n<p>The test command would be like this:</p>\n<pre><code class=\"language-sh\">$ node --import=tsx --test '**/*.test.*'\n</code></pre>\n<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>\n<p>After the migration, you can use Node.js test runner's mocking APIs.</p>\n<p>But you need a bit hard work for Module Mocking. (and you need\n<code>--experimental-test-module-mocks</code> flag)</p>\n<p>The former example would be like the following:</p>\n<p><code>src/foo/foo.test.ts</code></p>\n<pre><code class=\"language-ts\">// no mocks are used in this file.\n\nimport { foo } from &quot;./foo&quot;;\n\nimport assert from &quot;node:assert/strict&quot;;\nimport { describe, it } from &quot;node:test&quot;;\n\ndescribe(&quot;foo()&quot;, () =&gt; {\n  it(&quot;returns 'foo'&quot;, () =&gt; {\n    assert.equal(foo(), &quot;foo&quot;);\n  });\n});\n</code></pre>\n<p><code>src/foo/foo-bar.test.ts</code></p>\n<pre><code class=\"language-ts\">// mocks are used in this file.\n\nimport assert from &quot;node:assert/strict&quot;;\nimport { beforeEach, describe, it, mock } from &quot;node:test&quot;;\n\ndescribe(&quot;fooBar()&quot;, () =&gt; {\n  let barMock = mock.fn&lt;() =&gt; string&gt;();\n  let fooBar: () =&gt; string;\n\n  beforeEach(async () =&gt; {\n    // This can stub function\n    mock.module(&quot;./bar&quot;, {\n      namedExports: { bar: barMock },\n    });\n\n    // Load the target file after mocking\n    ({ fooBar } = await import(&quot;./foo.js&quot;));\n  });\n\n  it(&quot;returns 'fooXXX'&quot;, () =&gt; {\n    barMock.mock.mockImplementation(() =&gt; &quot;XXX&quot;);\n\n    assert.equal(fooBar(), &quot;fooXXX&quot;);\n  });\n});\n</code></pre>\n<p>I hope the Module Mocking API will be better in the future.</p>\n","date_published":"Wed, 02 Apr 2025 00:00:00 GMT"},{"id":"https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/","url":"https://blog.mmyoji.com/posts/2023/07-01-improve-legacy-app-structure/","title":"Improve Legacy Node.js App Structure","content_html":"<!--more-->\n<p>A main project I currently tackle on adopts DDD architecture. But nobody in the\nproject understands it correctly:</p>\n<ul>\n<li>business logic spreads across everywhere</li>\n<li>empty Entities just like DTO</li>\n<li>Service classes with no business logic</li>\n<li>etc.</li>\n</ul>\n<p>Due to this situation, I'm now trying to improve application structure. Of\ncource, it's the best to use a framework that has strict rules about app/dir\nstructure. The project is a legacy, express.js based one, tho.</p>\n<p>One important thing is <strong>reducing roles (layers)</strong>. There're many layers and\nobjects in the current architecture. Everyone confuses what/where things should\nbe put. And I realized it makes sense that Ruby on Rails adopts MVC patterns.</p>\n<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>\n<p>One of the ideas will be based on NestJS approach (see\n<a href=\"https://docs.nestjs.com/first-steps\">the tutorial</a>).</p>\n<p>Web applications are normally the following 4 steps:</p>\n<ol>\n<li>receives a request</li>\n<li>validates it</li>\n<li>applies business logic</li>\n<li>returns a response.</li>\n</ol>\n<p>To acheive this, app structure can be like this:</p>\n<pre><code class=\"language-sh\">app/\n  posts/\n    posts.router.ts\n    posts.handlers.ts\n    posts.validator.ts\n    posts.service.ts\n</code></pre>\n<ul>\n<li><code>posts.router.ts</code> handles <code>/posts/*</code> requests.</li>\n<li><code>posts.service.ts</code> hanldes a part of <code>3.</code></li>\n</ul>\n<pre><code class=\"language-ts\">// posts.router.ts\nexport const postsRouter = defineRouter(&quot;/posts&quot;, (r) =&gt; {\n  // GET /posts\n  r.get(&quot;/&quot;, indexHandler);\n\n  // GET /posts/:id\n  r.get(&quot;/:id&quot;, showHandler);\n\n  // GET /posts/new\n  r.get(&quot;/new&quot;, newHandler);\n\n  // POST /posts\n  r.post(&quot;/&quot;, createHandler);\n\n  // GET /posts/:id/edit\n  r.get(&quot;/:id/edit&quot;, editHandler);\n\n  // PATCH /posts/:id\n  r.patch(&quot;/:id&quot;, updateHandler);\n\n  // DELETE /posts/:id\n  r.delete(&quot;/:id&quot;, destroyHandler);\n});\n\n// posts.handlers.ts\nexport const indexHandler = defineHandler(async (req, res) =&gt; {\n  const options = validateSearchOptions(req.query);\n  const { posts, total, cursor } = await service.fetchList(options);\n  res.json({ data: { posts }, total, cursor });\n});\n\nexport const createHandler = defineHandler(async (req, res) =&gt; {\n  const data = validatePost(req.body);\n  const post = await service.create(data);\n  res.status(201).json({ data: { post } });\n});\n</code></pre>\n<p>You can also arrange the structure like this:</p>\n<pre><code class=\"language-sh\">app/\n  posts/\n    posts.router.ts\n    posts.service.ts\n    _index/\n      index.handler.ts\n      index.validator.ts\n    _create/\n      create.handler.ts\n      create.validator.ts\n</code></pre>\n<p>it can be like the following for more realistic monolith apps:</p>\n<pre><code class=\"language-sh\">apps/\n  admin/   # admin.example.com/\n    posts/\n      posts.router.ts\n      # ...\n  web/     # www.example.com/\n    posts/\n      posts.router.ts\n      # ...\n</code></pre>\n<p>Other <em>common</em> layers like persistent layer, middlewares, logger, etc. is like\nthis:</p>\n<pre><code class=\"language-sh\">app/\n  # ...\nlib/\n  entities/\n    post.ts\n    user.ts\n  middlewares/\n    not-found.middleware.ts\n    server-error.middleware.ts\n  repositories/\n    post.repository.ts\n    user.repository.ts\n  logger.ts\n  define-handler.ts\n  define-router.ts\n</code></pre>\n<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>\n<p>I'd thought Repository pattern would be better than ActiveRecord pattern for\nseveral years. But I have to consider using AR if project members aren't skilled\nenough.</p>\n<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>\n<p>I'm just trying this now and don't know what I feel in future.</p>\n","date_published":"Sat, 01 Jul 2023 00:00:00 GMT"},{"id":"https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/","url":"https://blog.mmyoji.com/posts/2023/03-09-apply-eslint-rules-gradually/","title":"Apply ESLint rules gradually to your legacy project","content_html":"<!--more-->\n<p>It's not hard at all to introduce <a href=\"https://eslint.org/\">ESLint</a> to a new\nproject, but so hard for a legacy project.</p>\n<p>Although there are several ways to do this, I'll leave a better way for myself.</p>\n<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>\n<ol>\n<li>Upgrade eslint version as far as possible</li>\n<li>List up <code>&quot;rules&quot;</code> w/ <code>&quot;error&quot;</code></li>\n<li>Add TODO list in <code>&quot;overrides&quot;</code> per directory w/ <code>&quot;warn&quot;</code></li>\n<li>Run with\n<a href=\"https://eslint.org/docs/latest/use/command-line-interface#--max-warnings\"><code>--max-warnings=0</code></a>\noption to newly added files on CI</li>\n</ol>\n<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>\n<p>If eslint libs are installed in your project, you should upgrade them as far as\npossible.</p>\n<p>The rules may behave differently and have better options after the upgrade.</p>\n<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>\n<p>After upgrading or installing the eslint libs, you would set up <code>.eslintrc.js</code>.</p>\n<p>By the way, I'm explaining this with the old eslint config file in this post.\nBut the technique itself is also applicable to new config file.</p>\n<pre><code class=\"language-js\">// .eslintrc.js\n\nmodule.exports = {\n  // ...\n\n  rules: {\n    &quot;@typescript-eslint/no-implicit-any&quot;: [&quot;error&quot;],\n  },\n  // ...\n};\n</code></pre>\n<p>In this phase, your project may not have passed <code>eslint</code> on CI, but it's OK for\nnow.</p>\n<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>\n<p>To mitigate the violations, you can list up the directory list with <code>&quot;warn&quot;</code>.</p>\n<p>Suppose your project is built using the MVC style.</p>\n<pre><code class=\"language-js\">// .eslintrc.js\n\nmodule.exports = {\n  // ...\n\n  overrides: [\n    // TODO: remove listed dir after fixing the violations.\n    {\n      files: [\n        &quot;src/controllers/admin/**/*.ts&quot;,\n        &quot;src/controllers/web/**/*.ts&quot;,\n        &quot;src/models/**/*.ts&quot;,\n        &quot;src/lib/**/*.ts&quot;,\n      ],\n      rules: {\n        &quot;@typescript-eslint/no-implicit-any&quot;: [&quot;warn&quot;],\n      },\n    },\n  ],\n};\n</code></pre>\n<p>For example,</p>\n<ol>\n<li>fix <code>no-implicit-any</code> violations under the <code>src/controlles/admin/*</code></li>\n<li>remove the line <code>&quot;src/controllers/admin/**/*.ts&quot;</code></li>\n<li>violations can be detected under the directory</li>\n</ol>\n<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>\n<p>This is an optional, but very effective.</p>\n<p>You can apply stricter rules to new files. This can be done with the following\ncommand:</p>\n<pre><code class=\"language-shell\"># `remotes/origin/main` can be replaced with your base branch.\n$ eslint \\\n    --max-warnings=0 \\\n    $(git diff --name-only --diff-filter=A remotes/origin/main HEAD)\n</code></pre>\n<p>ref:\n<a href=\"https://stackoverflow.com/a/15535048\">https://stackoverflow.com/a/15535048</a></p>\n<p>If the new files are violated against any <code>&quot;warn&quot;</code> rules, they are detected as\n<code>&quot;error&quot;</code>.</p>\n<p>You can avoid violated files are added in your project.</p>\n","date_published":"Thu, 09 Mar 2023 00:00:00 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/09-21-migrate-to-lume/","url":"https://blog.mmyoji.com/posts/2022/09-21-migrate-to-lume/","title":"Migrate to lume","content_html":"<p>I've migrated the static site generator for this blog from\n<a href=\"https://gohugo.io/\">hugo</a> to <a href=\"https://lume.land/\">lume</a>.</p>\n<!--more-->\n<p>I had been looking for an alternative static site generator built in Deno and\n<a href=\"https://github.com/denoland/deno_blog\">deno_blog</a> was a candidate. But it\ncurrently doesn't support pagination\n(<a href=\"https://github.com/denoland/deno_blog/pull/73\">p-r exists</a> tho).</p>\n<p>lume is almost the same as hugo, at least for me, it's enough. First I thought I\nneed to design entire site by myself, but I found\n<a href=\"https://github.com/lumeland/theme-simple-blog\">theme-simple-blog</a> can be used\nas a plugin (like hugo theme).</p>\n<p>Then I tried to migrate my blog to lume and I could. It just took less than an\nhour.</p>\n<p>The steps was like following:</p>\n<ol>\n<li>Run init command (<code>deno run -A https://deno.land/x/lume/init.ts</code>) under the\nroot of the blog repo.</li>\n<li>Remove extra <code>content</code> directory for the posts</li>\n<li>Remove unnecessary hugo files</li>\n<li>Add <code>_data.yml</code></li>\n<li>Add my own <code>about.md</code></li>\n<li>Update <code>.gitignore</code></li>\n<li>Add <code>them-simple-blog</code> plugin</li>\n</ol>\n<p>I've also migrated the hosting from <a href=\"https://netlify.com/\">Netlfiy</a> to\n<a href=\"https://deno.com/deploy\">Deno Deploy</a>.</p>\n<p>See the setup detail\n<a href=\"https://lume.land/docs/advanced/deployment/#deno-deploy\">here</a>.</p>\n<p>I am now satified with it :)</p>\n","date_published":"Wed, 21 Sep 2022 00:00:00 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/09-21-next-fetch-redirect/","url":"https://blog.mmyoji.com/posts/2022/09-21-next-fetch-redirect/","title":"How to enable redirect in next-fetch","content_html":"<!--more-->\n<p><a href=\"https://github.com/vercel-labs/next-fetch\">next-fetch</a> is useful when you use\nboth SWR and Next.js API Routes.</p>\n<p>But currently the documentation is poor. I am a bit struggled with how to use\n<code>hookResponse</code> option in query or mutation.</p>\n<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>\n<p>⚠️ This is just for <code>@next-fetch/swr@0.0.2</code> and could be changed in future\nversions.</p>\n<p>For API routes, see\n<a href=\"https://next-fetch-pi.vercel.app/swr#add-a-mutation-to-your-api-endpoint\">the doc</a>.</p>\n<pre><code class=\"language-tsx\">// pages/form.tsx\n\nexport default function MyFormPage() {\n  return (\n    &lt;form action=&quot;/api/simple?__handler=useMutation&quot; method=&quot;POST&quot;&gt;\n      &lt;input type=&quot;text&quot; name=&quot;name&quot; /&gt;\n      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;\n    &lt;/form&gt;\n  );\n}\n</code></pre>\n","date_published":"Wed, 21 Sep 2022 00:00:00 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/","url":"https://blog.mmyoji.com/posts/2022/06-30-nextjs-edge-api-routes/","title":"Next.js Edge API Routes","content_html":"<!--more-->\n<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>\n<p><code>runtime: &quot;experimental-edge&quot;</code> behaves differently from <code>runtime: &quot;nodejs&quot;</code>.</p>\n<p>It returns different HTTP response headers and I will explain the detail in this\npost (although this might be wrong partially.)</p>\n<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>\n<p><a href=\"https://nextjs.org/blog/next-12-2\">Next.js v12.2</a> introduces\n<a href=\"https://nextjs.org/docs/api-reference/edge-runtime\">Edge Runtime</a>.</p>\n<p>I'd just understood it couldn't use Node.js standard APIs and was lightweight.</p>\n<p>But I was not sure how this worked and how differed from <code>runtime: &quot;nodejs&quot;</code> and\nwondered whether this affected when choosing self-hosted.</p>\n<p>The official document says like this:</p>\n<blockquote>\n<p>Edge API Routes can stream responses from the server and run after cached\nfiles (e.g. HTML, CSS, JavaScript) have been accessed.</p>\n</blockquote>\n<p>...OK, I will test the new API routes!</p>\n<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>\n<p>Set <code>experimental.runtime: &quot;nodejs&quot;</code> in <code>next.config.js</code></p>\n<p><code>pages/api/test.ts</code> uses <code>runtime: &quot;nodejs&quot;</code>:</p>\n<pre><code class=\"language-ts\">import { NextApiHandler } from &quot;next&quot;;\n\nconst handler: NextApiHandler = async (_req, res) =&gt; {\n  res.status(200).json({ name: &quot;Jim Halpert&quot; });\n};\n\nexport default handler;\n</code></pre>\n<p><code>pages/api/test-edge.ts</code> uses <code>runtime: &quot;experimental-edge&quot;</code>:</p>\n<pre><code class=\"language-ts\">import { NextApiHandler } from &quot;next&quot;;\n\nexport const config = {\n  runtime: &quot;experimental-edge&quot;,\n};\n\nconst handler: NextApiHandler = async (_req, res) =&gt; {\n  // see: https://nextjs.org/docs/api-routes/edge-api-routes#json-response\n  return new Response(\n    JSON.stringify({\n      name: &quot;Jim Halpert&quot;,\n    }),\n    {\n      status: 200,\n      headers: {\n        &quot;Content-Type&quot;: &quot;application/json; charset=utf-8&quot;,\n      },\n    },\n  );\n};\n\nexport default handler;\n</code></pre>\n<p>Both of them returns the same response body.</p>\n<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>\n<p>I picked up only response headers part.</p>\n<p><code>curl -v http://localhost:xxx/api/test</code></p>\n<pre><code class=\"language-txt\">&lt; HTTP/1.1 200 OK\n&lt; Content-Type: application/json; charset=utf-8\n&lt; ETag: &quot;16-DlZOPLD8Q/zEnYFRYsdmnqlUPWI&quot;\n&lt; Content-Length: 22\n&lt; Vary: Accept-Encoding\n&lt; Date: Thu, 30 Jun 2022 11:42:50 GMT\n&lt; Connection: keep-alive\n&lt; Keep-Alive: timeout=5\n</code></pre>\n<p><code>curl -v http://localhost:xxx/api/test-edge</code></p>\n<pre><code class=\"language-txt\">&lt; HTTP/1.1 200 OK\n&lt; Content-Type: application/json; charset=utf-8\n&lt; Date: Thu, 30 Jun 2022 11:43:39 GMT\n&lt; Connection: keep-alive\n&lt; Keep-Alive: timeout=5\n&lt; Transfer-Encoding: chunked\n</code></pre>\n<p><code>experimental-edge</code> runtime:</p>\n<ul>\n<li>lacks <code>ETag</code>, <code>Content-Length</code>, and <code>Vary</code> headers</li>\n<li>adds <code>Tranfer-Encoding: chunked</code> header</li>\n</ul>\n<p><code>Transfer-Encoding: chunked</code> sends HTTP repsonse as <code>chunk</code> (or stream)\naccording to\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding\">MDN doc</a>.</p>\n<ul>\n<li>With this header, <code>Content-Length</code> is omitted from the doc.</li>\n<li>It's understandable <code>Vary: Accepct-Encoding</code> is omitted for this endpoint\nbecause <code>Transfer-Encoding</code> is specified (chunks are always sent).</li>\n<li>The reason <code>ETag</code> is dropped, I guess, might be that the endpoint could not\nend in a single request.</li>\n</ul>\n<p>Again, my understanding for HTTP is not enough and this post might be wrong.\nPlease check correct sources by yourself.</p>\n<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>\n<ul>\n<li><a href=\"https://nextjs.org/blog/next-12-2\">Blog - Next.js 12.2 | Next.js</a></li>\n<li><a href=\"https://nextjs.org/docs/api-reference/edge-runtime\">Edge Runtime | Next.js</a></li>\n<li><a href=\"https://nextjs.org/docs/api-routes/edge-api-routes#json-response\">API Routes: Edge API Routes (Beta) | Next.js</a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag\">ETag - HTTP | MDN</a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary\">Vary - HTTP | MDN</a></li>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding\">Transfer-Encoding - HTTP | MDN</a></li>\n</ul>\n","date_published":"Thu, 30 Jun 2022 11:21:04 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/","url":"https://blog.mmyoji.com/posts/2022/05-31-activate-wslg-in-pengwin/","title":"Activate WSLg in Pengwin","content_html":"<p>I tried <a href=\"https://github.com/microsoft/wslg\">WSLg</a> because I might need to run\n<code>chromedriver</code> for next job.</p>\n<p>I had ever struggled and gave up running <code>chromedriver</code> in normal WSL(WSL2)\nenvironment before, so I thought this was a good opportunity.</p>\n<p>The following step is limited for WSL2 +\n<a href=\"https://github.com/WhitewaterFoundry/Pengwin\">Pengwin</a> distro that I've been\nusing.</p>\n<!--more-->\n<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>\n<ul>\n<li>OS: Windows 11, 21H2, 22000.708</li>\n<li>GPU: GeForce RTX 2080</li>\n</ul>\n<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>\n<p>ref:\n<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>\n<ol>\n<li>Install <a href=\"https://developer.nvidia.com/cuda/wsl\">NVIDIA GPU driver for WSL</a></li>\n<li>Install required packages in WSL</li>\n</ol>\n<pre><code class=\"language-sh\">pengwin-setup update\n\n# Open setup dialog\n# and select [GUI], then install necessary packages.\npengwin-setup\n</code></pre>\n<p>After package installation, restart WSL (or restart Windows).</p>\n<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>\n<p>ref: https://github.com/microsoft/wslg#install-and-run-gui-apps</p>\n<pre><code class=\"language-sh\">cd /tmp\nsudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb\nsudo dpkg -i google-chrome-stable_current_amd64.deb\nsudo apt install --fix-broken -y\nsudo dpkg -i google-chrome-stable_current_amd64.deb\n\n# Start Google Chrome app in Linux GUI for test\ngoogle-chrome\n</code></pre>\n<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>\n<p>ref: https://tecadmin.net/setup-selenium-with-chromedriver-on-debian/</p>\n<pre><code class=\"language-sh\"># 1. check your google chrome version on WSLg\n# 2. Download the same version of chromedriver with google-chrome\n#    http://chromedriver.storage.googleapis.com/index.html\n#    The version was 102.0.5005.61 this time.\n\ncd /tmp\nwget http://chromedriver.storage.googleapis.com/102.0.5005.61/chromedriver_linux64.zip\nunzip chromedriver_linux64.zip\nsudo mv chromedriver /usr/bin/chromedriver\nsudo chown root:root /usr/bin/chromedriver\nsudo chmod +x /usr/bin/chromedriver\n</code></pre>\n<p>Run the following script when you want to test.</p>\n<p>ref: https://www.npmjs.com/package/selenium-webdriver</p>\n<pre><code class=\"language-js\">/**\n * $ npm init -y\n * $ npm install selenium-webdriver\n * $ vim main.js\n */\n\n// main.js\nconst { Builder, Browser, By, Key, until } = require(&quot;selenium-webdriver&quot;);\n\n(async function main() {\n  const driver = await new Builder().forBrowser(Browser.CHROME).build();\n  try {\n    await driver.get(&quot;http://www.google.com/ncr&quot;);\n    await driver.findElement(By.name(&quot;q&quot;)).sendKeys(&quot;webdriver&quot;, Key.RETURN);\n    await driver.wait(until.titleIs(&quot;webdriver - Google Search&quot;), 1000);\n    // Replace the string with 'webdriver - Google 検索' when your language setting is Japanese.\n  } finally {\n    await driver.quit();\n  }\n})();\n\n// node main.js\n</code></pre>\n","date_published":"Tue, 31 May 2022 00:02:42 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/05-29-disable-all-inputs-in-form/","url":"https://blog.mmyoji.com/posts/2022/05-29-disable-all-inputs-in-form/","title":"Disable all inputs in form","content_html":"<p>When you want to disable all <code>input</code>s in <code>form</code> after submitting, you can use\n<code>fieldset</code> for the purpose.</p>\n<!--more-->\n<pre><code class=\"language-html\">&lt;form action=&quot;#&quot;&gt;\n  &lt;fieldset disabled&gt;\n    &lt;!-- like this --&gt;\n    &lt;legend&gt;Sign in&lt;/legend&gt;\n\n    &lt;label for=&quot;email&quot;&gt;Email&lt;/label&gt;\n    &lt;input id=&quot;email&quot; name=&quot;email&quot; type=&quot;email&quot;&gt;\n\n    &lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;\n    &lt;input id=&quot;password&quot; name=&quot;password&quot; type=&quot;password&quot;&gt;\n\n    &lt;input type=&quot;submit&quot;&gt;\n    &lt;!-- button is also disabled\n      &lt;button&gt;Submit&lt;/button&gt;\n    --&gt;\n  &lt;/fieldset&gt;\n&lt;/form&gt;\n</code></pre>\n<p>see:\n<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>\n","date_published":"Sun, 29 May 2022 00:24:07 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/","url":"https://blog.mmyoji.com/posts/2022/05-26-transform-stream-in-deno/","title":"Transform Stream in Deno","content_html":"<p>I wrote Node.js version of this: <a href=\"https://blog.mmyoji.com/posts/2022/01-24-transform-stream/\">see</a>.</p>\n<p><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Streams_API\">Streams APIs</a> are\nbit different from <code>node:stream</code>.</p>\n<!--more-->\n<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>\n<pre><code class=\"language-typescript\">interface Post {\n  id: number;\n  title: string;\n}\n\nconst posts: Post[] = [\n  { id: 1, title: &quot;a&quot; },\n  { id: 2, title: &quot;b&quot; },\n  { id: 3, title: &quot;c&quot; },\n  { id: 4, title: &quot;d&quot; },\n  { id: 5, title: &quot;e&quot; },\n  { id: 6, title: &quot;f&quot; },\n  { id: 7, title: &quot;g&quot; },\n  { id: 8, title: &quot;h&quot; },\n  { id: 9, title: &quot;i&quot; },\n  { id: 10, title: &quot;j&quot; },\n];\n\n// Dummy ORM API\nconst db = {\n  post: {\n    findMany({ take, skip }: { take: number; skip: number }): Promise&lt;Post[]&gt; {\n      return Promise.resolve(posts.slice(skip, skip + take));\n    },\n  },\n};\n\nfunction postStream(take: number): ReadableStream&lt;Post&gt; {\n  let skip = 0;\n\n  return new ReadableStream&lt;Post&gt;(\n    {\n      async pull(controller) {\n        const posts = await db.post.findMany({\n          skip,\n          take,\n        });\n        for (const post of posts) {\n          controller.enqueue(post);\n        }\n\n        if (posts.length &lt; take) {\n          controller.close();\n          return;\n        }\n\n        skip = skip + take;\n      },\n    },\n    { highWaterMark: take },\n  );\n}\n\nfunction toCSV(take: number): TransformStream&lt;Post, string&gt; {\n  return new TransformStream&lt;Post, string&gt;(\n    {\n      start(controller) {\n        controller.enqueue(`id,title`);\n      },\n      transform(chunk, controller) {\n        controller.enqueue(`${chunk.id},${chunk.title}`);\n      },\n      flush(controller) {\n        controller.terminate();\n      },\n    },\n    {\n      highWaterMark: take,\n    },\n  );\n}\n\nconst take = 3;\nconst stream = postStream(take).pipeThrough(toCSV(take));\nfor await (const chunk of stream) {\n  console.log(chunk);\n}\n</code></pre>\n<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>\n<ul>\n<li><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Streams_API\">Streams API - Web APIs | MDN</a></li>\n<li><a href=\"https://web.dev/streams/\">Streams—The definitive guide</a></li>\n</ul>\n","date_published":"Thu, 26 May 2022 08:32:22 GMT"},{"id":"https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/","url":"https://blog.mmyoji.com/posts/2022/05-24-handling-large-json-file-in-deno/","title":"Handling large JSON file in Deno","content_html":"<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>\n<p>Avoid using large JSON file, but use CSV or other easy-to-parse file format for\nstream API.</p>\n<p>Please tell me if you have a better solution.</p>\n<!--more-->\n<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>\n<p>When you have to handle large file (mainly in server side), you normally use\n<a href=\"https://nodejs.org/api/stream.html\">Stream API</a> in Node.js.</p>\n<p>In Deno, you don't have it but use\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Streams_API\">Web Streams API</a>\ninstead. (Actually you could use <code>std/node</code> libraries:\n<a href=\"https://deno.land/manual/node/std_node\">see</a>)</p>\n<p>Stream handles data as <code>chunk</code>s, but JSON file, an object rather than an array\nof JSON objects especially, is hard to be handled.</p>\n<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>\n<p>This is a sample code to read JSON file as string in Deno.</p>\n<pre><code class=\"language-ts\">// tmp/\n//   test.ts\n//   test.json\n\nimport { readableStreamFromReader } from &quot;https://deno.land/std@0.140.0/streams/mod.ts&quot;;\n\nconst file = await Deno.open(&quot;./tmp/test.json&quot;, { read: true });\n\n// Uint8Array\nconst byteStream = readableStreamFromReader(file);\n\n// Uint8Array -&gt; String\nconst decodedStream = byteStream.pipeThrough(new TextDecoderStream());\n\nfor await (const chunk of decodedStream) {\n  console.log({ chunk: JSON.parse(chunk) });\n}\n\n// $ deno run --allow-read ./tmp/test.ts\n</code></pre>\n<p>If the file is not so large, the chunk is its entire content as string. But if\nthe file is large enough, the chunk is not JSON-parsable one.</p>\n<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>\n<p>There is (was) a library called\n<a href=\"https://www.npmjs.com/package/JSONStream\">JSONParse</a> on npm. But it's not\nmaintained yet and only supports Node.js environment.</p>\n<p>So there are several options:</p>\n<ol>\n<li>Write your own parser: like as\n<a href=\"https://stackoverflow.com/questions/58070346/reading-large-json-file-in-deno\">this stackoverflow post</a></li>\n<li>Use other file format (like CSV):\n<a href=\"https://c2fo.github.io/fast-csv/\">fast-csv</a> (found in\n<a href=\"https://esm.sh/fast-csv\">esm.sh</a>) is a good library</li>\n<li>Avoid using large JSON</li>\n</ol>\n<p>I rarely have experienced like this but I've tried thinking about it for\nexercise.</p>\n","date_published":"Tue, 24 May 2022 05:59:21 GMT"}]}