{
    "version": "https://jsonfeed.org/version/1",
    "title": "Craig's Deno Diary",
    "home_page_url": "https://deno-blog.com/",
    "feed_url": "https://deno-blog.com/feed?format=json",
    "description": "Craig's Deno Diary is a blog that covers the JavaScript and TypeScript runtime Deno and focuses on how to write Deno programs and use Deno libraries.",
    "icon": "https://deno-blog.com/img/deno-diary-logo_smallcircle.png",
    "items": [
        {
            "id": "https://deno-blog.com/Testing_Fresh_Components,_Middleware_and_Handlers_with_fresh-testing-library.2023-10-15",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2023-10-15\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2023-10-15\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2023-10-15</h4><h5 id=\"22-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#22-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>22 min read</em></h5><h1 id=\"testing-fresh-components-middleware-and-handlers-with-fresh-testing-library\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-fresh-components-middleware-and-handlers-with-fresh-testing-library\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing Fresh Components, Middleware and Handlers with fresh-testing-library</h1><h2 id=\"table-of-contents\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#table-of-contents\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Table of contents</h2><ul>\n<li><a href=\"#introduction\">Introduction</a></li>\n<li><a href=\"#example-code\">Example Code</a></li>\n<li><a href=\"#component-testing\">Component Testing</a><ul>\n<li><a href=\"#setting-up-a-fresh-testing-library-component-test\">Setting up a fresh-testing-library component test</a><ul>\n<li><a href=\"#using-assertions-for-test-verification\">Using assertions for test verification</a></li>\n<li><a href=\"#running-tests\">Running tests</a></li>\n</ul>\n</li>\n<li><a href=\"#rendering-components-under-test\">Rendering components under test</a></li>\n<li><a href=\"#finding-dom-elements\">Finding DOM Elements</a><ul>\n<li><a href=\"#text-argument-options\">Text Argument Options</a></li>\n<li><a href=\"#uses-of-get-query-and-find-functions\">Uses of get*, query* and find* Functions</a></li>\n<li><a href=\"#using-byrole-finder-functions\">Using ByRole finder functions</a></li>\n<li><a href=\"#accessibility-testing\">Accessibility testing</a></li>\n</ul>\n</li>\n<li><a href=\"#testing-user-interactions\">Testing User Interactions</a><ul>\n<li><a href=\"#using-fireevent\">Using <code>fireEvent</code></a></li>\n<li><a href=\"#using-userevent\">Using <code>userEvent</code></a></li>\n</ul>\n</li>\n<li><a href=\"#testing-component-state-management\">Testing component state management</a></li>\n<li><a href=\"#component-testing-troubleshooting-tips\">Component testing troubleshooting tips</a></li>\n</ul>\n</li>\n<li><a href=\"#middleware-and-route-handler-testing\">Middleware and Route Handler testing</a><ul>\n<li><a href=\"#testing-middleware\">Testing middleware</a></li>\n<li><a href=\"#testing-route-handlers\">Testing route handlers</a></li>\n<li><a href=\"#testing-async-route-components\">Testing async route components</a></li>\n</ul>\n</li>\n<li><a href=\"#conclusion\">Conclusion</a></li>\n<li><a href=\"#acknowledgements\">Acknowledgements</a></li>\n</ul>\n<h1 id=\"introduction\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#introduction\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Introduction</h1><p>The <a href=\"https://deno.land/x/fresh_testing_library\" rel=\"noopener noreferrer\"><code>fresh-testing-library</code></a> is a\nnew unit testing utility for Deno Fresh. Until now testing a Fresh application\nused the built-in Deno test runner with <code>Deno.test</code> and <code>std/testing</code> utilities\nfor verifying low-level logic and the\n<a href=\"https://deno.land/x/puppeteer\" rel=\"noopener noreferrer\">Deno-Puppeteer</a> or\n<a href=\"https://astral.deno.dev/\" rel=\"noopener noreferrer\">Astral</a> libs for end-to-end testing. The\n<code>fresh-testing-library</code> fills the niche between the other options to allow\nisolated testing of fresh components, middleware and route handlers.</p>\n<p><code>fresh-testing-library</code> creates a thin wrapper around the\n<a href=\"https://preactjs.com/guide/v10/preact-testing-library/\" rel=\"noopener noreferrer\">Preact Testing Library</a>\nwhich is built upon the\n<a href=\"https://testing-library.com/docs/dom-testing-library/intro\" rel=\"noopener noreferrer\">DOM Testing Library</a>.\nAll of them including <code>fresh-testing-library</code> share an API under the\n<a href=\"https://testing-library.com/\" rel=\"noopener noreferrer\">Testing Library</a> moniker.</p>\n<p>The <code>fresh-testing-library</code> also adds utilities for testing Fresh route\nhandler's, route components and middleware. Those are under the <code>server.ts</code>\nmodule while the component testing library code is in the <code>component.ts</code> module.\nBoth can also be accessed via the <code>mod.ts</code> module.</p>\n<p>The Testing Library philosophy is to create tests that interact with the\napplication the same way an app user would do. To do so, Testing Library tests\nhone in on verifying DOM elements.</p>\n<p>Testing library also focusses on accessibility, offering a number of functions\nto find elements by accessible attributes.</p>\n<p>The <code>fresh-testing-library</code> is registered as a Deno third-party library under\nthe \"<a href=\"https://deno.land/x/fresh_testing_library&quot;\" rel=\"noopener noreferrer\">https://deno.land/x/fresh_testing_library\"</a> URL.</p>\n<h2 id=\"example-code\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#example-code\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Example Code</h2><p>This blog post will focus on how to use the <code>fresh-testing-library</code>. With that\nin mind, I have created example code in several repos. They are:</p>\n<ul>\n<li>The repo for this blog: <a href=\"https://github.com/cdoremus/deno-blog/tree/main/tests\" rel=\"noopener noreferrer\">https://github.com/cdoremus/deno-blog/tree/main/tests</a>\n(8 test files)</li>\n<li>The repo for the blog post I did on using signals with Fresh:\n<a href=\"https://github.com/cdoremus/fresh-todo-signals/tree/main/tests\" rel=\"noopener noreferrer\">https://github.com/cdoremus/fresh-todo-signals/tree/main/tests</a> (6 test files)</li>\n<li>The web site components, middleware &amp; route handling in the Fresh repo:\n<a href=\"https://github.com/cdoremus/fresh/tree/fresh-testing-lib/tests/www/\" rel=\"noopener noreferrer\">https://github.com/cdoremus/fresh/tree/fresh-testing-lib/tests/www/</a>\n(15 test files)</li>\n</ul>\n<h1 id=\"component-testing\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#component-testing\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Component testing</h1><h2 id=\"setting-up-a-fresh-testing-library-component-test\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#setting-up-a-fresh-testing-library-component-test\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Setting up a fresh-testing-library component test</h2><p>Using <code>fresh-testing-library</code> for a component test requires the Deno test runner\nand the <code>testing/bdd.ts</code> module of the Deno standard library. The <code>bdd</code> module\nadds functions that are familiar to testing in Node.js using libraries like\n<a href=\"https://jestjs.io/\" rel=\"noopener noreferrer\">Jest</a> or <a href=\"https://mochajs.org/\" rel=\"noopener noreferrer\">Mocha</a>. They include\n<code>describe</code>, <code>it</code>, <code>beforeAll</code>, <code>afterAll</code>, <code>beforeEach</code> and <code>afterEach</code>.</p>\n<p>Here is a simple annotated example of a <code>fresh-testing-library</code> component test\nthat illustrates test setup and code:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Adapted from Todo.test.tsx in Fresh</span>\n<span class=\"token comment\">//   signals blog post repo</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  cleanup<span class=\"token punctuation\">,</span>\n  fireEvent<span class=\"token punctuation\">,</span>\n  render<span class=\"token punctuation\">,</span>\n  setup<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/fresh_testing_library/component.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  afterEach<span class=\"token punctuation\">,</span>\n  beforeAll<span class=\"token punctuation\">,</span>\n  describe<span class=\"token punctuation\">,</span>\n  it<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/std/testing/bdd.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> assertEquals <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/std/assert/mod.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Todo <span class=\"token keyword\">from</span> <span class=\"token string\">\"../islands/Todo.tsx\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Todo.tsx test\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// setup the jsdom environment</span>\n  <span class=\"token function\">beforeAll</span><span class=\"token punctuation\">(</span>setup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// unmount rendered component after each test</span>\n  <span class=\"token function\">afterEach</span><span class=\"token punctuation\">(</span>cleanup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should display todo...\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// assign the component's text content via a prop</span>\n    <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token string\">\"Foo\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// render a component's DOM markup</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Todo text<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>text<span class=\"token punctuation\">}</span> index<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token number\">1</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// find an HTML element using the rendered component's text content</span>\n    <span class=\"token keyword\">const</span> textElement <span class=\"token operator\">=</span> <span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// verify element's text content</span>\n    <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>textElement<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">,</span> text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"using-assertions-for-test-verification\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-assertions-for-test-verification\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using assertions for test verification</h3><p>Functions in the <a href=\"https://deno.land/std/assert\" rel=\"noopener noreferrer\"><code>assert</code></a> module of the Deno standard library works for <code>fresh-testing-library</code> verifications. Anyone who has done any Deno testing will be familiar with them.  However, in version 0.10.0 of the <code>fresh-testing-library</code> the <code>expect</code> function was added to <code>components.ts</code>. Like others, this function comes from Preact Testing Library.</p>\n<p>The <code>expect</code> function has a number of matcher functions attached to it that do the verification. They have a <a href=\"https://jasmine.github.io/api/edge/matchers.html\" rel=\"noopener noreferrer\">Jasmine-like API</a>. Some of them behave similarly to the Deno-native assert functions.</p>\n<p>Here are some <code>expect</code> function matchers and their Deno-native equivalent (if there is one):</p>\n<table>\n<thead>\n<tr>\n<th>expect function matcher</th>\n<th>Deno-native assert</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>toBeInTheDocument()</td>\n<td>assertExists()/assertEquals()</td>\n</tr>\n<tr>\n<td>toBe()</td>\n<td>assertEquals()</td>\n</tr>\n<tr>\n<td>not.toBe()</td>\n<td>assertNotEquals()</td>\n</tr>\n<tr>\n<td>toBeTruthy()</td>\n<td>assert()</td>\n</tr>\n<tr>\n<td>toBeFalsy()</td>\n<td>assertFalse()</td>\n</tr>\n<tr>\n<td>toBeEmptyDOMNode</td>\n<td>[none]</td>\n</tr>\n<tr>\n<td>toBeRequired</td>\n<td>[none]</td>\n</tr>\n</tbody></table>\n<p>Here's a simple example how to use <code>expect</code> with <code>fresh-testing-library</code>:</p>\n<div class=\"highlight\"><pre>  <span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> expect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/fresh_testing_library/expect.ts\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should find 'Hello World' text in document\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> queryByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>div<span class=\"token operator\">&gt;</span>Hello World<span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello World\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeInTheDocument</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// using getByText will error out before matchers are called</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"foobar\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>not<span class=\"token punctuation\">.</span><span class=\"token function\">toBeInTheDocument</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>Note that you can use the <code>not</code> matcher to negate a matcher that it calls.</p>\n<p>The <code>expect</code> function contains a number of matcher functions that check on function calls (like <code>expect(foo).toBeCalled()</code>). To get them to work, you need to mock the functions. The <code>jest-mock</code> library's <code>fn</code> function was incorporated into the <code>expect.ts</code> module in v0.11.0. You can also use <code>fn</code> to mock return values. Here's a simple example how that works:</p>\n<div class=\"highlight\"><pre>  <span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> expect<span class=\"token punctuation\">,</span> fn <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/fresh_testing_library/expect.ts\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should be able to use mock functions\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> add <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>num1<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">,</span> num2<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">number</span> <span class=\"token operator\">=&gt;</span> num1 <span class=\"token operator\">+</span> num2<span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">add</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span><span class=\"token number\">6</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBe</span><span class=\"token punctuation\">(</span><span class=\"token number\">8</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// create mock impl of add</span>\n    <span class=\"token keyword\">const</span> mockAdd <span class=\"token operator\">=</span> <span class=\"token function\">fn</span><span class=\"token punctuation\">(</span>add<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">mockImplementation</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>num1<span class=\"token punctuation\">,</span> num2<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> num1 <span class=\"token operator\">*</span> num2<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> sum <span class=\"token operator\">=</span> <span class=\"token function\">mockAdd</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">,</span> <span class=\"token number\">2</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>sum<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBe</span><span class=\"token punctuation\">(</span><span class=\"token number\">4</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>mockAdd<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeCalled</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>mockAdd<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>not<span class=\"token punctuation\">.</span><span class=\"token function\">toBeCalledTimes</span><span class=\"token punctuation\">(</span><span class=\"token number\">2</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span><span class=\"token function\">mockAdd</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBe</span><span class=\"token punctuation\">(</span><span class=\"token number\">15</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">expect</span><span class=\"token punctuation\">(</span>mockAdd<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">toBeCalledWith</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span><span class=\"token punctuation\">,</span> <span class=\"token number\">5</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>If you need to mock a function that is a dependency of the component-under-test, send the mock function into the component as a prop. The real (non-mock) dependency would be sent in from the parent component so the component will work correctly at runtime.</p>\n<p>The <a href=\"https://github.com/cdoremus/deno-blog/blob/main/tests/components/expect.test.tsx\" rel=\"noopener noreferrer\"><code>expect.test.tsx</code></a> file in this blog's repo has some examples of how to use <code>expect</code> and <code>fn</code>.</p>\n<p>There are also other functions from the <code>jest-mock</code> library incorporated into the <code>expect.ts</code> module. They include <code>mocked</code>, <code>replaceProperty</code>, and <code>spyOn</code>.</p>\n<h3 id=\"running-tests\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#running-tests\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Running tests</h3><p>Run <code>fresh-testing-library</code> tests with this command line:</p>\n<pre><code>deno test --allow-read --allow-env</code></pre><p>Both permission flags are required because the <code>jsdom</code> library is used\ninternally in component tests. You might also need to add other permission flags\ndepending on the code you are testing.</p>\n<h2 id=\"rendering-components-under-test\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#rendering-components-under-test\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Rendering components under test</h2><p>The first thing you need in a <code>fresh-testing-library</code> test is a call to the\nlibrary's <code>render</code> function. It is used to instantiate the component-under-test.\nIts first argument is required and is the JSX representation of a component\nincluding its props.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> render <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https:/deno.land/x/fresh_testing_library/component.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// renders component DOM wrapped inside a document.body</span>\n<span class=\"token keyword\">const</span> screen <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Counter count<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token number\">1</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>render</code> function returns an object that contains functions for verifying a\ncomponent's content. Instead of returning that object ( <code>screen</code> in this case),\nyou can destructure the functions required to find the content. Here's an\nexample of a text search with <code>getByText</code> destructured from <code>render</code>:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> render <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https:/deno.land/x/fresh_testing_library/component.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// renders component DOM wrapped inside a document.body</span>\n<span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Counter cont<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token number\">1</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello World!\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>render</code> function has an optional second argument that is rarely used. For\nmore information, see the\n<a href=\"https://github.com/testing-library/preact-testing-library/blob/0e5bf3790d3e7d45bdfbd8a0ccc0a0ab4495798d/types/index.d.ts#L16\" rel=\"noopener noreferrer\"><code>RenderOptions</code> interface in the <code>preact-testing-library</code> type documentation</a>.</p>\n<h2 id=\"finding-dom-elements\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#finding-dom-elements\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Finding DOM Elements</h2><p>As stated previously, Testing Library libs including <code>fresh-testing-library</code>\nfocus on verifying DOM elements generated by the component-under-test. These\nelements create the UI including interaction components.</p>\n<p>Finder functions are found on the <a href=\"https://github.com/testing-library/preact-testing-library/blob/main/types/index.d.ts#L7\" rel=\"noopener noreferrer\"><code>RenderResult</code></a> object that is returned from a\ncall to <code>render</code>.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> screen<span class=\"token operator\">:</span> RenderResult <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Features <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> screen<span class=\"token punctuation\">.</span><span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello World!\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>Discovering DOM elements in a <code>fresh-testing-library</code> test requires use of a\nfinder. Finder function names have three parts:</p>\n<ul>\n<li>The finder mechanism: 'get', 'query' or 'find'</li>\n<li>Is the search for single or multiple nodes: 'By' or 'AllBy'</li>\n<li>How to find an element: 'AltText', 'DisplayValue', 'LabelText',\n 'PlaceholderText', 'Role', 'TestId' or 'Title'</li>\n</ul>\n<p>Combined, the first two parts of a finder function name indicates what the\nfunction returns. Here's how that works:</p>\n<table>\n<thead>\n<tr>\n<th>Finder function</th>\n<th>Match Found returns</th>\n<th>Match Not Found returns</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>getBy*</td>\n<td>matching element</td>\n<td>throws an error</td>\n</tr>\n<tr>\n<td>getAllBy*</td>\n<td>matching element array</td>\n<td>throws an error</td>\n</tr>\n<tr>\n<td>queryBy*</td>\n<td>matching element</td>\n<td>null</td>\n</tr>\n<tr>\n<td>queryAllBy*</td>\n<td>matching element array</td>\n<td>empty array</td>\n</tr>\n<tr>\n<td>findBy*</td>\n<td>resolved Promise  (element)</td>\n<td>rejected Promise</td>\n</tr>\n<tr>\n<td>findAllBy*</td>\n<td>resolved Promise (element array)</td>\n<td>rejected Promise</td>\n</tr>\n</tbody></table>\n<p>Also note that if you are using the singular 'getBy', 'queryBy' or 'findBy'\nfunctions and more than one element is returned, then an error is thrown.\nConversely, a getAllBy, queryAllBy and findAllBy function returns an array with\na single element if only one matching item exists.</p>\n<p>I need to interject here that while the table above -- and other documentation -- points out that an element (HTMLElement) gets returned, if the element has child elements (like an HTML form), then the whole node gets returned. However, most of the time you'll want to fashion the finder function to find a single element.</p>\n<p>The full name of a finder function specifies how an element is located. Here's\nwhat they are with a 'getBy' prefix (the same functions exist using 'queryBy' or\n'findBy'):</p>\n<ul>\n<li><code>getByAltText</code> - finds the element using the <code>alt</code> attribute</li>\n<li><code>getByDisplayValue</code> - finds an <code>input</code>, <code>textarea</code> or <code>select</code> element using\nthe <code>value</code> attribute.</li>\n<li><code>getByLabelText</code> - finds the element using the <code>label</code> attribute. Obviously, this is mostly used to locate form elements wrapped in a <code>label</code>.</li>\n<li><code>getByPlaceholderText</code> - finds the element using the <code>placeholder</code> attribute\nfound in a <code>text</code> or <code>textarea</code> form element.</li>\n<li><code>getByRole</code> - finds the element by its <code>role</code> attribute or implicit role\n(<a href=\"#using-byrole-finder-functions\">see below</a>).</li>\n<li><code>getByTestId</code> - finds an element using the <code>data-testid</code> attribute</li>\n<li><code>getByTitle</code> - finds an element using the enclosed <code>title</code>element. This is\nbest used with an svg graphic that contains a <code>title</code> child element.</li>\n</ul>\n<p>All of the finder functions have the same first two arguments. The first one is\nan HTML element which would be obtained from a previous call to a finder\nfunction. Most of the time you will not need this argument.</p>\n<p>The second argument is used to match the the HTML element being searched for. It\nis either a number, string, regular expression or a function\n(<a href=\"#matching-text\">see next section</a>). The 'ByRole' method only takes specific\nstring values (<a href=\"#using-byrole-finder-functions\">see below</a>).</p>\n<p>Finder functions have an optional third argument which is an options object. The\nproperties of that argument is specific to the finder.</p>\n<h3 id=\"text-argument-options\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#text-argument-options\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Text Argument Options</h3><p>Most finder functions take a\n<a href=\"https://testing-library.com/docs/queries/about/#textmatch\" rel=\"noopener noreferrer\"><code>TextMatch</code></a> which\ncan be:</p>\n<ul>\n<li>a string</li>\n<li>a regex</li>\n<li>a function</li>\n</ul>\n<p>The <code>TextMatch</code> function has optional string and <code>HTMLElement</code> arguments and\nreturns a boolean (<code>true</code> for a match; <code>false</code> for no match). Here's an example\nof a test with functional matching:</p>\n<div class=\"highlight\"><pre><span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should show source code\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> code <span class=\"token operator\">=</span> <span class=\"token string\">\"console.log('Hello World')\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getByRole<span class=\"token punctuation\">,</span> getByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>CodeBox code<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>code<span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// find code</span>\n  <span class=\"token keyword\">const</span> codeElement <span class=\"token operator\">=</span> <span class=\"token function\">getByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"code\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// get the text content</span>\n  <span class=\"token keyword\">const</span> content <span class=\"token operator\">=</span> codeElement<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Prism library breaks up code for styling purposes</span>\n  <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>\n    <span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>content<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> content<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"console\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>\n    <span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>content<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> content<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"log\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>\n    <span class=\"token function\">getByText</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>content<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> content<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Hello World\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>A finder function that takes a <code>TextMatch</code> can contain an options object with an\n<code>exact</code> or <code>normalization</code> property that affects the precision of the match.\nThere is a\n<a href=\"https://testing-library.com/docs/queries/about/#precision\" rel=\"noopener noreferrer\">section in the Testing Library docs that explores these options in detail</a>,\nbut here they are in a nutshell:</p>\n<ul>\n<li><code>exact</code> - (<code>true</code> by default) determines whether the match is case-sensitive or\nnot.</li>\n<li><code>normalization</code> - By default whitespace is collapsed when doing a text match. This property\ncan be set to override that behavior.</li>\n</ul>\n<h2 id=\"uses-of-get-query-and-find-functions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#uses-of-get-query-and-find-functions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Uses of get*, query* and find* Functions</h2><p>As shown above, the first part of a finder function name indicates what gets\nreturned when you call the function. For that reasons, each one of them has a\nfavored use</p>\n<ul>\n<li><p><strong>get</strong>* - use to find elements to be able to verify their attributes or\ncontent or invoke an event. You will probably use this most of the time in a\ncomponent test.</p>\n</li>\n<li><p><strong>query</strong>* - should only be used to verify the existence or non-existence of\nan element. Otherwise, a 'get*' finder should be used.</p>\n</li>\n</ul>\n<p>For example, I have a test where I want to make sure that no <code>Todo</code> components\nwill be shown. Each <code>Todo</code> contains a <code>button</code> element. I use <code>queryAllByRole</code> to test for an empty array returned by that function. If I used <code>getAllByRole</code>, an error would be thrown\nif nothing was returned.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// TodoList.test.tsx in Fresh signals post code repo</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should not display list of todos...\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> todos <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token keyword\">as</span> <span class=\"token\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  state<span class=\"token punctuation\">.</span>todos<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> todos<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> queryAllByRole <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span>\n    <span class=\"token operator\">&lt;</span>AppState<span class=\"token punctuation\">.</span>Provider value<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>state<span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>TodoList <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>AppState<span class=\"token punctuation\">.</span>Provider<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// queryAllBy returns an empty array; getAllBy just throws an error</span>\n  <span class=\"token keyword\">const</span> buttons <span class=\"token operator\">=</span> <span class=\"token function\">queryAllByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>buttons<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">,</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><ul>\n<li><strong>find</strong>* - use when an async operation or rendering delay would prevent an element or elements from appearing in the UI. Fetching remote data is an example. Another example is when an action causes a rerender. Here's how <code>findBy</code> is used:</li>\n</ul>\n<div class=\"highlight\"><pre><span class=\"token comment\">// MenuLink.test.tsx in this blog's repo</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Show About link\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// change the location, triggering a menu rerender</span>\n  globalThis<span class=\"token punctuation\">.</span>window<span class=\"token punctuation\">.</span>location <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> href<span class=\"token operator\">:</span> <span class=\"token string\">\"/about\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> findByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>MenuLink <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// findByText returns a Promise</span>\n  <span class=\"token keyword\">const</span> page <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">findByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Home\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> page<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertNotEquals</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">,</span> <span class=\"token string\">\"About\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><ul>\n<li><strong>waitFor</strong> While not strictly a finder function, <code>waitFor</code> is used under the\ncovers by the <strong>findBy</strong>* finder. However, it can be used standalone.</li>\n</ul>\n<p>The\n<a href=\"https://testing-library.com/docs/dom-testing-library/api-async/#waitfor\" rel=\"noopener noreferrer\"><code>waitFor</code> function</a>\nis used for verifying DOM elements that take a while to render due to an async\noperation. Here's how <code>waitFor</code> is used:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">await</span> <span class=\"token function\">waitFor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>counter<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">,</span> count<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>As shown, <code>waitFor</code> is an async function that takes a function argument and\noptionally an options argument. This function runs until the waited-for\noperation succeeds or times out. The timeout is 1000ms by default, but that can\nbe changed in the options argument's <code>timeout</code> property.</p>\n<h2 id=\"using-byrole-finder-functions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-byrole-finder-functions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using ByRole finder functions</h2><p>Kent C. Dodds, the creator of Testing Library, recommends to\n<a href=\"https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-byrole-most-of-the-time\" rel=\"noopener noreferrer\">prefer the use of the 'ByRole' finder function to discover DOM nodes</a>. This is a tough thing to do since <code>role</code> options are not well documented in Testing Library docs. Instead, a role used by Testing Library is documented in the <a href=\"https://www.w3.org/TR/wai-aria/\" rel=\"noopener noreferrer\">WAI ARIA accessibility standard</a>.</p>\n<p>The 'ByRole' finder requires a string that is a valid WAI ARIA role name (see\nthis\n<a href=\"https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles\" rel=\"noopener noreferrer\">list of WAI ARIA roles</a>).</p>\n<p>Certain HTML elements have\n<a href=\"https://www.w3.org/TR/html-aria/#docconformance\" rel=\"noopener noreferrer\">an implicit role</a>. Some of\nthese elements have the same role name as the element name. They include\n<code>button</code>, <code>article</code>, <code>code</code> and <code>img</code>.</p>\n<p>Nearly all of the HTML form elements also have implicit roles. This includes\n<code>input</code> elements with <code>type=\"text\"</code> (<code>textbox</code> role), <code>type=\"radio\"</code> (<code>radio</code>\nrole), <code>type=\"range\"</code> (<code>slider</code> role), <code>type=\"number\"</code> (<code>spinbutton</code> role) and\n<code>type=\"submit\"</code> (<code>button</code> role). The <code>textarea</code> element's implicit role is\n<code>textbox</code> while the <code>select</code> element's implicit role is <code>listbox</code> or <code>combobox</code>.</p>\n<p>Another handy implicit role to use is <code>link</code> for an HTML anchor (<code>a</code> tag), but the anchor must have a <code>href</code> attribute. Here's an example of a component test to verify 3 links:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Header.test.tsx in Fresh repo</span>\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"components/gallery/Header.tsx\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">beforeAll</span><span class=\"token punctuation\">(</span>setup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">afterEach</span><span class=\"token punctuation\">(</span>cleanup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should show 3 links\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getAllByRole <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Header active<span class=\"token operator\">=</span><span class=\"token string\">\"\"</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> links <span class=\"token operator\">=</span> <span class=\"token function\">getAllByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"link\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>links<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>When you want to use an implicit role, do not set an element's <code>role</code> attribute.</p>\n<p>Sometimes you might have multiple elements with the same implicit role; multiple buttons, for instance. In that case use the options argument.</p>\n<p>Each role has a different set of options. All of them have a <code>name</code> property,\nbut the property's value depends on the role. For the <code>button</code> role, the <code>name</code>\nproperty is the button's value. For the <code>img</code> role, the <code>name</code> property is the\nvalue of the element's <code>alt</code> attribute. Roles for HTML elements without an\nimplicit role must be specified using the <code>role</code> attribute.</p>\n<p>Many IDEs like Visual Studio Code support discovery of valid roles using\nautocompletion. Just type <code>getByRole(\"\")</code> putting your cursor within the quotes. Then press ctrl/cmd-spacebar\nand a list of valid roles should appear.</p>\n<p>If you use an argument to a 'ByRole' finder that is illegal, when the test is\nrun the error message will point out the available implicit roles. Alternately,\nyou can use the <code>fresh-testing-library</code> function <code>logRole</code> to find the implicit\nroles within your component-under-test.</p>\n<h2 id=\"accessibility-testing\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#accessibility-testing\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Accessibility testing</h2><p>Testing Library emphasizes accessibility by having a number of finder functions\nthat use accessibility attributes. The 'ByRole' finder is a big one, but\n'ByPlaceholderText', 'ByAltText', 'ByLabel' and 'ByTitle' are others.</p>\n<p>Here is an example of using the <code>title</code> attribute with the 'ByTitle' finder:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Background.test.tsx in Fresh repo</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should display background image\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Background takes any number of native attributes or Preact props</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getByTitle <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Background title<span class=\"token operator\">=</span><span class=\"token string\">\"background\"</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> bg <span class=\"token operator\">=</span> <span class=\"token function\">getByTitle</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"background\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> style <span class=\"token operator\">=</span> bg<span class=\"token punctuation\">.</span><span class=\"token function\">getAttribute</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"style\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>style<span class=\"token punctuation\">,</span> <span class=\"token string\">\"background-image: url(/gallery/grid.svg);\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h2 id=\"testing-user-interactions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-user-interactions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing User Interactions</h2><p>The <code>fresh-testing-library</code> contains two ways for simulating user interactions:\n<code>fireEvent</code> and <code>userEvent</code>.</p>\n<p>The <code>fireEvent</code> function is used for simple interactions with the UI, while\n<code>userEvent</code> simulated the subtile nature of the interactions. For instance when a\nbutton is pressed, <code>fireEvent.click</code> would cover the DOM click event, but\n<code>userEvent.click</code> encompasses the hover, keydown and keyup events in addition to\nthe click event. In other words, <code>userEvent</code> more closely behaves the way users\nwould\n(<a href=\"https://blog.mimacom.com/react-testing-library-fireevent-vs-userevent/\" rel=\"noopener noreferrer\">see this article for more details</a>).</p>\n<h3 id=\"using-fireevent\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-fireevent\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using <code>fireEvent</code></h3><p>The <a href=\"https://testing-library.com/docs/dom-testing-library/api-events#fireevent\" rel=\"noopener noreferrer\"><code>fireEvent</code></a> object includes function properties for almost 90 DOM events\nThey include:</p>\n<ul>\n<li><code>fireEvent.click</code> - to invoke an <code>onClick</code> handler</li>\n<li><code>fireEvent.change</code> - to invoke an <code>onChange</code> handler</li>\n<li><code>fireEvent.submit</code> - to invoke an <code>onSubmit</code> handler</li>\n<li><code>fireEvent.keyDown</code> - to invoke an <code>onKeyDown</code> handler</li>\n</ul>\n<p>See the <a href=\"https://github.com/testing-library/dom-testing-library/blob/main/types/events.d.ts\" rel=\"noopener noreferrer\"><code>EventType</code> union type in the TypeScript docs</a> for an enumeration of\nall events.</p>\n<p>Each of the <code>fireEvent</code> function properties takes an argument that is the\n<code>HTMLElement</code> event target.</p>\n<p>Here's an example of using a 'click' event to invoke a button click:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Counter.test.tsx in Fresh signals blog post</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should display count and increment/decrement it correctly\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> count <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">signal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">number</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token number\">9</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> queryByRole<span class=\"token punctuation\">,</span> queryByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Counter count<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>count<span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> plusOne <span class=\"token operator\">=</span> <span class=\"token function\">queryByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"+1\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span>plusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> minusOne <span class=\"token operator\">=</span> <span class=\"token function\">queryByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"-1\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span>minusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">await</span> fireEvent<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span>plusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertFalse</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"9\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"10\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">await</span> fireEvent<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span>minusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"9\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertFalse</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"10\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this case, the event target are two button elements, one for incrementing the\ncount, the other for decrementing it.</p>\n<h3 id=\"using-userevent\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-userevent\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using <code>userEvent</code></h3><p>The <a href=\"https://testing-library.com/docs/user-event/intro\" rel=\"noopener noreferrer\"><code>userEvent</code></a> object has 16 function properties. They include <code>clear</code>, <code>click</code>,\n<code>copy</code>, <code>cut</code>, <code>dblClick</code>, <code>deselectOptions</code>, <code>hover</code>, <code>keyboard</code>, <code>paste</code>,\n<code>pointer</code>, <code>selectOptions</code>, <code>tab</code>, <code>tripleClick</code>, <code>type</code>, <code>unhover</code> and\n<code>upload</code>. As you can surmise from the function names, these correspond to actual\nways that the user interacts with the UI rather than strict DOM events.</p>\n<p>The arguments for these functions can be very different. Many take a DOM element like, <code>click</code>, <code>dblClick</code> and <code>hover</code>, while others have very different arguments. See the <a href=\"https://testing-library.com/docs/user-event/intro\" rel=\"noopener noreferrer\"><code>userEvent</code> docs</a> or use your IDE (hover over the function or right click and select \"Go to Definition\") to discover these args.</p>\n<p>The <code>userEvent</code> object has a <code>setup</code> function that should be used to instantiate\nthe object before it is used. Here's what that looks like:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> userEvent<span class=\"token punctuation\">.</span><span class=\"token function\">setup</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The previous <code>fireEvent</code> example can be adapted to use <code>userEvent</code>. Here is what\nit looks like:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Be sure to import userEvent from FTL component.ts</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should display count and increment/decrement it correctly\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> userEvent<span class=\"token punctuation\">.</span><span class=\"token function\">setup</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> count <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">signal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">number</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token number\">9</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> queryByRole<span class=\"token punctuation\">,</span> queryByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Counter count<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>count<span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> plusOne <span class=\"token operator\">=</span> <span class=\"token function\">queryByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"+1\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span>plusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> minusOne <span class=\"token operator\">=</span> <span class=\"token function\">queryByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"-1\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span>minusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">await</span> user<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span>plusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertFalse</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"9\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"10\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">await</span> user<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span>minusOne<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"9\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertFalse</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"10\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>While <code>userEvent</code> seems like a more logical choice since it focuses on user\ninteractions, <code>fireEvent</code> covers more event types so it needs to be used in\ncertain circumstances. For instance, <code>fireEvent</code> has a <code>change</code> function for\nsimulating an <code>onChange</code> event, while <code>userEvent</code> does not have a similar\nfunction.</p>\n<h2 id=\"testing-component-state-management\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-component-state-management\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing component state management</h2><p>Preact signals provide a means to do local and global state management with\nsignals. A previous\n<a href=\"https://deno-blog-stage.deno.dev/Using_Preact_Signals_with_Fresh.2022-11-01\" rel=\"noopener noreferrer\">Craig's Deno Diary post</a>\ndemonstrated how to use signals with Fresh.</p>\n<h3 id=\"testing-local-state\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-local-state\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing local state</h3><p>Compared to <code>useState</code>, the <code>signal</code> function only returns a single value and\nnot an array with a setter function. Updating the signal involves assigning the\nvalue property of the signal a new value.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> count <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">signal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">number</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token number\">0</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> newValue <span class=\"token operator\">=</span> count<span class=\"token punctuation\">.</span>value <span class=\"token operator\">+</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\ncount<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> newValue<span class=\"token punctuation\">;</span></pre></div><p>The local state often changes when an event gets invoked which in turn updates\nthe UI. In a <code>fresh-testing-library</code> test, the <code>fireEvent</code> function would\ntrigger his kind of change.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>getByRole<span class=\"token punctuation\">,</span> queryByText<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>Counter <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> button <span class=\"token operator\">=</span> <span class=\"token function\">getByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\nfireEvent<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span>button<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Count: 1\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"testing-global-state\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-global-state\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing global state</h3><p>Global state management with Preact signals requires a module that holds that\nstate and using the Preact context to pass the state to child components. I will\nnot go over how this is coded in detail as you can refer the the\n<a href=\"https://deno-blog.com/Using_Preact_Signals_with_Fresh.2022-11-01\" rel=\"noopener noreferrer\">Craig's Deno Diary post</a>\non how it's done.</p>\n<p>The code used to illustrate the state management testing shown here is taken\nfrom the\n<a href=\"https://github.com/cdoremus/fresh-todo-signals\" rel=\"noopener noreferrer\">signals blog post's repo</a>.</p>\n<p>Testing a component that uses global state requires that you wrap the component\nin a Preact context provider that passes the state into the component. The context is created in the parent component using the <code>createContext</code> function:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// App.tsx</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">const</span> AppState <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">createContext</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>AppStateType<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span> <span class=\"token keyword\">as</span> AppStateType<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>AppState</code> context has a provider field used to pass the state to child components.</p>\n<p>The child components discover the state using the <code>useContext</code> hook. Here's a\ncomponent that displays a list obtained from the context:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// TodoList.tsx in Fresh signals blog post repo</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> useContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"preact/hooks\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> AppState <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./App.tsx\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Todo <span class=\"token keyword\">from</span> <span class=\"token string\">\"./Todo.tsx\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">TodoList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> todos <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">useContext</span><span class=\"token punctuation\">(</span>AppState<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token operator\">&lt;</span>div className<span class=\"token operator\">=</span><span class=\"token string\">\"todos\"</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token punctuation\">{</span><span class=\"token comment\">/* todos are a signal that contain the todos value */</span><span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">{</span>todos<span class=\"token punctuation\">.</span>value<span class=\"token operator\">?.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>item<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">,</span> i<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token operator\">&lt;</span>Todo text<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>item<span class=\"token punctuation\">}</span> index<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>i<span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>To test a component in isolation, you need to wrap it in a context provider so\nthat state can be passed to the child component. Here's what that kind of test\nlooks like:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// TodoList.test.tsx in Fresh signals blog post repo</span>\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should display list of todos...\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> todos <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"Foo\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Bar\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Baz\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// state is imported from the module that holds it (state.ts)</span>\n  state<span class=\"token punctuation\">.</span>todos<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> todos<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> getAllByRole<span class=\"token punctuation\">,</span> queryByText <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span>\n    <span class=\"token operator\">&lt;</span>AppState<span class=\"token punctuation\">.</span>Provider value<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>state<span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>TodoList <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>AppState<span class=\"token punctuation\">.</span>Provider<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// verify that all todos are displayed</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> todos<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span>todos<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// Each todo has a delete button</span>\n  <span class=\"token keyword\">const</span> buttons <span class=\"token operator\">=</span> <span class=\"token function\">getAllByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>buttons<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">,</span> <span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>Notice in the test that we assign the state's <code>todos</code> value before we attached it to the <code>AppState.Provider</code> context provider.</p>\n<p>The\n<a href=\"https://deno-blog-stage.deno.dev/Using_Preact_Signals_with_Fresh.2022-11-01\" rel=\"noopener noreferrer\">original blog post on Fresh signals</a>\nhas been updated with <code>fresh-testing-library</code> component tests including the test\nused in this section.</p>\n<h2 id=\"component-testing-troubleshooting-tips\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#component-testing-troubleshooting-tips\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Component testing troubleshooting tips</h2><ul>\n<li>Manipulating <code>IS_BROWSER</code> - This constant is used a lot in Fresh app code, but\ntesting it can be problematic. In order to set <code>IS_BROWSER</code> to false, you need\nto set the <code>document</code> object to <code>undefined</code>. Doing that will cause a\n<code>fresh-testing-library</code> test to fail because the <code>jsdom</code> library cannot\nfunction without a valid <code>document</code> object. Therefore, testing code that uses\n<code>IS_BROWSER</code> cannot be done with <code>fresh-testing library</code>.</li>\n<li><code>debug</code> functions - used for printing out the DOM returned from calling\n<code>render</code> or any of the finder functions. The former prints out the complete\nDOM that was rendered and the latter prints out the returned DOM from a finder\nfunction call. Here's how to use them:</li>\n</ul>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> debug<span class=\"token punctuation\">,</span> queryByRole <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token operator\">&lt;</span>MyComponent <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// print out all the component's DOM elements wrapped in a body element</span>\n<span class=\"token function\">debug</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> element <span class=\"token operator\">=</span> <span class=\"token function\">queryByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"button\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// prints out the button element's DOM</span>\nelement<span class=\"token punctuation\">.</span><span class=\"token function\">debug</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h2 id=\"middleware-and-route-handler-testing\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#middleware-and-route-handler-testing\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Middleware and Route Handler testing</h2><h3 id=\"testing-middleware\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-middleware\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing middleware</h3><p>Testing middleware employs the <code>createMiddlewareHandlerContext</code> function to mock\nout the <code>MiddlewareHandlerContext</code> used in a middleware handler. Here's what the\ncode and a test would look like for the\n<a href=\"https://github.com/cdoremus/deno-blog/blob/main/routes/_middleware.ts\" rel=\"noopener noreferrer\">middleware in this repo</a>;</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// _middleware.ts in this blog's repo</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> MiddlewareHandlerContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$fresh/server.ts\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">const</span> handler <span class=\"token operator\">=</span> <span class=\"token function\">setCacheControlHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">setCacheControlHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>_req<span class=\"token operator\">:</span> Request<span class=\"token punctuation\">,</span> ctx<span class=\"token operator\">:</span> MiddlewareHandlerContext<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> resp <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    resp<span class=\"token punctuation\">.</span>headers<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Cache-Control\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"public, max-age=21600, immutable\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> resp<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// middleware.test.ts</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> assertEquals <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"std/assert/assert_equals.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createMiddlewareHandlerContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$fresh-testing-library\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> setCacheControlHeaders <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"../../routes/_middleware.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> manifest <span class=\"token keyword\">from</span> <span class=\"token string\">\"../../fresh.gen.ts\"</span><span class=\"token punctuation\">;</span>\n\nDeno<span class=\"token punctuation\">.</span><span class=\"token function\">test</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should set Cache-Control header\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> req <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Request</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">http://localhost:3000/</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// @ts-ignore ignores \"type ... is not assignable to type Manifest\" error</span>\n  <span class=\"token keyword\">const</span> ctx <span class=\"token operator\">=</span> <span class=\"token function\">createMiddlewareHandlerContext</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> manifest <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> resp <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Response</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> middleware <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">setCacheControlHeaders</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">await</span> <span class=\"token function\">middleware</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> ctx<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  resp <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> ctx<span class=\"token punctuation\">.</span><span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>\n    resp<span class=\"token punctuation\">.</span>headers<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Cache-control\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token string\">\"public, max-age=21600, immutable\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>You can use <code>Deno.test</code> in this case since a middleware test does not require\nsetup or cleanup.</p>\n<p>The <code>Request</code> object can be used to find out the current URL or request method\n(GET, POST, etc) for middleware with logic that is more sophisticated than my\nsimple example.</p>\n<h3 id=\"testing-route-handlers\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-route-handlers\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing route handlers</h3><p>At this point, route testing using <code>fresh-testing-library</code> is confined to\ntesting handler logic and not the handler's <code>Response</code> including verifying\nresponse markup.</p>\n<p>You use the <code>createHandlerContext</code> function in <code>fresh-testing-library</code> to test\nthe handler function of a route component.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// about.test.ts in this blog's repo</span>\nDeno<span class=\"token punctuation\">.</span><span class=\"token function\">test</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"routes/About.tsx handler tests...\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>t<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">await</span> t<span class=\"token punctuation\">.</span><span class=\"token function\">step</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should respond to GET request in DEV...\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>handler<span class=\"token punctuation\">.</span><span class=\"token\">GET</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> req <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Request</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http://localhost:8000/about\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// @ts-ignore manifest typing</span>\n    <span class=\"token keyword\">const</span> ctx <span class=\"token operator\">=</span> <span class=\"token function\">createHandlerContext</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> manifest <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">const</span> res <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> handler<span class=\"token punctuation\">.</span><span class=\"token\">GET</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> ctx<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>res<span class=\"token punctuation\">.</span>status<span class=\"token punctuation\">,</span> <span class=\"token number\">200</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// ... other tests here</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>You do not need <code>startup</code> and <code>cleanup</code> functions for handler tests, so we use\nthe <code>Deno.test</code> runner function here instead of the <code>bdd</code> module functions.</p>\n<p>Alternatively, the Fresh codebase includes the <code>createHandler</code> function for\ntesting a route handler. The <code>createHandler</code> function creates a mock handler\nfunction.\n<a href=\"https://fresh.deno.dev/docs/examples/writing-tests\" rel=\"noopener noreferrer\">See the Fresh docs</a> for more details on using this function.</p>\n<p><a href=\"https://fresh.deno.dev/docs/concepts/routing#route-groups\" rel=\"noopener noreferrer\">Grouping routes</a> was\nadded in Fresh v1.4. Version 0.8.0, of the <code>fresh-testing-library</code> added support\nfor testing route groups. <code>fresh-testing-library</code> route handler tests\ntransparently works if routes are grouped.</p>\n<h2 id=\"testing-async-route-components\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#testing-async-route-components\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Testing async route components</h2><p><a href=\"https://fresh.deno.dev/docs/concepts/routes#async-route-components\" rel=\"noopener noreferrer\">Fresh async route components</a>\nare components where an async route handler function is inlined into the route\npage component. In that case, the route page component is declared as\nasynchronous (using the <code>async</code> keyword). Here's what that look likes (taken\nfrom the Fresh routes documentation):</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">MyPage</span><span class=\"token punctuation\">(</span>req<span class=\"token operator\">:</span> Request<span class=\"token punctuation\">,</span> ctx<span class=\"token operator\">:</span> RouteContext<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> value <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">loadFooValue</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token operator\">&lt;</span>p<span class=\"token operator\">&gt;</span>foo <span class=\"token keyword\">is</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>value<span class=\"token punctuation\">}</span><span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>p<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Fresh also ships with a\n<a href=\"https://fresh.deno.dev/docs/concepts/routes#define-helper\" rel=\"noopener noreferrer\"><code>defineRoute</code></a>\nfunction to simplify the creation of async route components.</p>\n<p>A <code>fresh-testing-library</code> test of an async route component uses the\n<code>createRouteContext</code> function in the <code>server</code> module. Here's how it is used in a\n<a href=\"https://github.com/uki00a/fresh-testing-library#testing-async-route-components\" rel=\"noopener noreferrer\"><code>fresh-testing-library</code></a>\nexample:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  cleanup<span class=\"token punctuation\">,</span>\n  getByText<span class=\"token punctuation\">,</span>\n  render<span class=\"token punctuation\">,</span>\n  setup<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$fresh-testing-library/components.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createRouteContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$fresh-testing-library/server.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> assertExists <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$std/assert/assert_exists.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> afterEach<span class=\"token punctuation\">,</span> beforeAll<span class=\"token punctuation\">,</span> describe<span class=\"token punctuation\">,</span> it <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"$std/testing/bdd.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">as</span> UserDetail <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./demo/routes/users/[id].tsx\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">as</span> manifest <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./demo/fresh.gen.ts\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"routes/users/[id].tsx\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">beforeAll</span><span class=\"token punctuation\">(</span>setup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">afterEach</span><span class=\"token punctuation\">(</span>cleanup<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"should work\"</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> req <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Request</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http://localhost:8000/users/2\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> ctx <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">createRouteContext</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> manifest <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> screen <span class=\"token operator\">=</span> <span class=\"token function\">render</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">await</span> <span class=\"token function\">UserDetail</span><span class=\"token punctuation\">(</span>req<span class=\"token punctuation\">,</span> ctx<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> group <span class=\"token operator\">=</span> screen<span class=\"token punctuation\">.</span><span class=\"token function\">getByRole</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"group\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token function\">assertExists</span><span class=\"token punctuation\">(</span><span class=\"token function\">queryByText</span><span class=\"token punctuation\">(</span>group<span class=\"token punctuation\">,</span> <span class=\"token string\">\"bar\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this case, <code>UserDetails</code> is the async route component.</p>\n<h2 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h2><p>Use the <a href=\"#example-code\">example code given at the beginning of this blog post</a>\nto guide you through the the core <code>fresh-testing-library</code> concepts introduced\nabove. There are over two dozen test files for you to peruse in those three\nrepos.</p>\n<p>There are also examples in the\n<a href=\"https://github.com/uki00a/fresh-testing-library\" rel=\"noopener noreferrer\"><code>fresh-testing-library</code> repo</a>.\nCheck out the <code>components.test.tsx</code>, <code>server.test.tsx</code> and <code>routes.test.tsx</code>\nfiles used to test code in the <code>demo</code> folder.</p>\n<p>Also be aware that <code>fresh-testing-library</code> has not yet released version 1.0, so\nit will continue to evolve to include more features and bug fixes. In addition,\nI know by first-hand experience that the <code>fresh-testing-library</code> author is very\nresponsive to issues submitted to the library's repo.</p>\n<p>Finally, since the component testing feature of <code>fresh-testing-library</code> wraps\nthe Preact Testing Library, you need to be familiar with that\n<a href=\"https://testing-library.com/docs/preact-testing-library/intro\" rel=\"noopener noreferrer\">library's documentation</a>.\nIt provides a guide to the full component testing API.</p>\n<h3 id=\"acknowledgements\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#acknowledgements\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Acknowledgements</h3><p>Special thanks goes to <code>fresh-testing-library</code> author Yuki Tanaka (@uki00a) for\nanswering my many questions, promptly fixing bug reports that I submitted and\nreviewing a draft of this article. I would also like to thank Kyle June (@kylejune) for his help with understanding the limitations of mocking with <code>expect</code>.</p>\n",
            "url": "https://deno-blog.com/Testing_Fresh_Components,_Middleware_and_Handlers_with_fresh-testing-library.2023-10-15",
            "title": "Testing Fresh Components, Middleware and Handlers with fresh-testing-library",
            "summary": "Testing Fresh Components, Middleware and Handlers with fresh-testing-library",
            "date_modified": "2023-10-15T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/A_Comprehensive_Guide_to_Deno_KV.2023-06-30",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h3 id=\"2023-06-30\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2023-06-30\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2023-06-30</h3><h5 id=\"36-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#36-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>36 min read</em></h5><h1 id=\"a-comprehensive-guide-to-deno-kv\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#a-comprehensive-guide-to-deno-kv\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>A Comprehensive Guide to Deno KV</h1><h2 id=\"table-of-contents\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#table-of-contents\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Table of contents</h2><ul>\n<li><a href=\"#introduction\">Introduction</a></li>\n<li><a href=\"#indexes\">Indexes</a></li>\n<li><a href=\"#keys-values-and-versionstamp\">Keys, values and versionstamp</a><ul>\n<li><a href=\"#kv-keys\">Keys</a></li>\n<li><a href=\"#kv-values\">Values</a></li>\n<li><a href=\"#kv-versionstamp\">Versionstamp</a></li>\n</ul>\n</li>\n<li><a href=\"#crud-operations\">CRUD operations</a><ul>\n<li><a href=\"#crud-data\">CRUD data</a></li>\n<li><a href=\"#create-set\">Create (<code>set()</code>)</a></li>\n<li><a href=\"#read-get\">Read (<code>get()</code>)</a></li>\n<li><a href=\"#update-set\">Update (<code>set()</code>)</a></li>\n<li><a href=\"#delete-delete\">Delete (<code>delete()</code>)</a></li>\n</ul>\n</li>\n<li><a href=\"#reading-multiple-records-list--getmany\">Reading multiple records (<code>list()</code> &amp; <code>getMany()</code>)</a><ul>\n<li><a href=\"#reading-a-list-from-kv-with-list\">Reading a list from KV with <code>list()</code></a><ul>\n<li><a href=\"#1-the-first-list-argument-selector\">The first <code>list()</code> argument: <code>selector</code></a></li>\n<li><a href=\"#2-the-second-list-argument-options\">The second <code>list()</code> argument: <code>options</code></a></li>\n<li><a href=\"#pagination-with-list\">Pagination with <code>list()</code></a></li>\n<li><a href=\"#paginate-kv-results-in-a-webapp\">Paginate KV results in a webapp</a></li>\n</ul>\n</li>\n<li><a href=\"#combining-records-from-multiple-indexes-with-getmany\">Combining records from multiple indexes with <code>getMany()</code></a></li>\n</ul>\n</li>\n<li><a href=\"#transactions-with-atomic\">Transactions with <code>atomic()</code></a><ul>\n<li><a href=\"#using-check-to-validate-transactions\">Using <code>check()</code> to validate transactions</a></li>\n<li><a href=\"#using-set-and-delete-for-transactional-persistance\">Using <code>set()</code> and <code>delete()</code> for transactional persistance</a></li>\n<li><a href=\"#persisting-data-in-a-kv-transaction\">Persisting data in a KV transaction</a></li>\n<li><a href=\"#tracking-a-records-history\">Tracking a record's history</a></li>\n<li><a href=\"#transaction-failures\">Transaction failures</a></li>\n</ul>\n</li>\n<li><a href=\"#secondary-indexes\">Secondary Indexes</a><ul>\n<li><a href=\"#creation\">Creation</a></li>\n<li><a href=\"#updating-and-deletion\">Updating and deletion</a></li>\n<li><a href=\"#sorting-with-indexes\">Sorting with indexes</a></li>\n</ul>\n</li>\n<li><a href=\"#math-operations-sum-min--max\">Math operations: sum, min &amp; max</a><ul>\n<li><a href=\"#sum-min-and-max-methods\"><code>sum()</code>, <code>min()</code> and <code>max()</code> methods</a></li>\n<li><a href=\"#mutate-method\"><code>mutate()</code> method</a></li>\n</ul>\n</li>\n<li><a href=\"#using-kv-queue\">Using KV Queue</a></li>\n<li><a href=\"#kv-on-deno-deploy\">KV on Deno Deploy</a><ul>\n<li><a href=\"#deno-deploy-data-centers\">Deno Deploy Data Centers</a><ul>\n<li><a href=\"#data-consistency\">Data Consistency</a></li>\n<li><a href=\"#synchronization-between-data-centers\">Synchronization Between Data Centers</a></li>\n</ul>\n</li>\n<li><a href=\"#loading-kv-data-into-deno-deploy\">Loading KV data into Deno Deploy</a></li>\n</ul>\n</li>\n<li><a href=\"#conclusions\">Conclusions</a></li>\n<li><a href=\"#appendix\">Appendix</a><ul>\n<li><a href=\"#references\">References</a><ul>\n<li><a href=\"#deno-manual-and-api-docs\">Deno Manual and API Docs</a></li>\n<li><a href=\"#apps-that-use-deno-kv\">Apps that use Deno KV</a></li>\n<li><a href=\"#kv-tools-under-development\">KV Tools under development</a></li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<hr />\n<h2 id=\"introduction\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#introduction\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Introduction</h2><p>Deno KV, a key-value based database, was built into the Deno runtime starting with Deno 1.32.0 as an unstable API. Deno Deploy now incorporates Deno KV (currently as an invite-only beta), and distributes KV data around the world. This means that when a web application is put on Deploy, there will now be a database close to each server instance. Deno KV also provides <a href=\"https://en.wikipedia.org/wiki/ACID\" rel=\"noopener noreferrer\">ACID</a> transactions to maintain data consistency.</p>\n<p>This article will cover all aspects of Deno KV with simple, easy-to-understand examples. Since info on Deno KV is in <a href=\"#deno-manual-and-api-docs\">multiple places in the Deno documentation</a>, consider this post as a one-stop guide to KV.</p>\n<p>Note that a dozen working code examples have been created to support this blog post. They can be found in <a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/deno-kv\" rel=\"noopener noreferrer\">this repository folder</a>. You'll also find each example linked in the relevant section of this article.</p>\n<h2 id=\"indexes\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#indexes\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Indexes</h2><p>An index in Deno KV parlance are the units of data storage. In relational database (RDBMS) terms, they can be loosely thought of as a table, but they are more like a SQL index because they are ordered by key value for fast lookup.</p>\n<p>A KV <strong>primary index</strong> is the index that stores a record using a unique key for each record, usually a UUID. We'll talk more about them in the next few sections. A <strong>secondary index</strong> stores additional information or is used for sorting. <a href=\"#secondary-indexes\">We'll talk about them later</a>.</p>\n<h2 id=\"keys-values-and-versionstamp\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#keys-values-and-versionstamp\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Keys, values and versionstamp</h2><p>Deno KV have well-defined keys, values and a versionstamp that represents a value's version.</p>\n<h3 id=\"kv-keys\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#kv-keys\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><a href=\"https://deno.com/manual@v1.34.0/runtime/kv/key_space#keys\" rel=\"noopener noreferrer\">KV Keys</a></h3><p>As stated above, Deno KV is a key-value database. In it's simplest form, a database record's data is persisted and found using the key. In Deno KV, the key is a tuple, an array with a fixed length. Each of the members of the tuple is called a <strong>part</strong>. All parts are linked together into a kind of compound key. Key parts can be of types <code>string</code>, <code>number</code>, <code>boolean</code>, <code>Uint8Array</code>, or <code>bigint</code>.</p>\n<p>Here are a couple of examples of a Deno KV key used to identify unique records:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// User key</span>\n<span class=\"token keyword\">const</span> userKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userid<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Used for storing user objects</span>\n<span class=\"token comment\">// Address key for a particular User</span>\n<span class=\"token keyword\">const</span> addressKey <span class=\"token punctuation\">[</span><span class=\"token string\">\"address\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// Used for storing a user's address</span></pre></div><p>As stated above, when a unique key is used to persist values, the resulting index is know as a primary index. The Web platform provides <code>crypto.getRandomUUID()</code> to obtain a unique id.</p>\n<p>An index is an ordered sequence of key parts so that <code>[1, \"user\"]</code> is not the same as <code>[\"user\", 1]</code>.</p>\n<p>When used in a primary index the first key part is usually a string constant identifying the model collection being persisted, <code>\"user\"</code> or <code>\"address\"</code> in the example. When used to add records to an index, the key parts are combined into a compound key.</p>\n<p>The initial key part of an index can be expanded into multiple parts. For instance, if we have a user with roles, we might have the second part representing a role such as:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userAdminKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"admin\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> userCustomerKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"customer\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> userGuestKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"guest\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span></pre></div><p>If there is a role field on the model, the previous keys could be reduced to something like this (assuming each user has one role):</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userRoleKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_role\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userRole<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span></pre></div><p>Indexes created with these keys are called secondary indexes. They need to be persisted in an atomic transaction with the primary index in order for the data to be is consistent between indexes (<a href=\"#secondary-indexes\">see the secondary index discussion below</a>). Secondary indexes should have a first part name describing their function. In this case, the key is used to lookup a user and the first part is <code>\"user_by_role\"</code>.</p>\n<h3 id=\"kv-values\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#kv-values\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><a href=\"https://deno.com/manual@v1.34.0/runtime/kv/key_space#values\" rel=\"noopener noreferrer\">KV Values</a></h3><p>In order for Deno KV values to be persisted, a value must be a structured, serializable JavaScript type compatible with <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm\" rel=\"noopener noreferrer\">JavaScript's structured clone algorithm</a>.</p>\n<p>Basically, it is anything that can be passed as the first argument of <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/structuredClone\" rel=\"noopener noreferrer\"><code>structuredClone()</code></a> except <code>SharedArrayBuffer</code>. The <code>SharedArrayBuffer</code> is an exception because is a shared memory data structure that cannot be passed across KV remote isolates.</p>\n<p>See  <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#things_that_dont_work_with_structured_clone\" rel=\"noopener noreferrer\">this discussion on MDN</a> for more details on what does not conform to the structured clone algorithm.</p>\n<p>Despite these limitations most common JavaScript types including <code>undefined</code>, <code>null</code>, <code>boolean</code>, <code>number</code>, <code>string</code>, <code>Array</code>, <code>Map</code>, <code>Uint8Array</code> and <code>Object</code> work as KV values (<a href=\"https://deno.com/manual@v1.34.0/runtime/kv/key_space#values\" rel=\"noopener noreferrer\">a full list</a>).</p>\n<p>The Deno Manual notes that objects with a non-primitive prototype such as class instances or Web API objects are not supported as KV values.</p>\n<p>Here are examples of how the keys and values are used to persist to Deno KV (the <code>set()</code> method will be discussed later):</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// persist an object with id, name and role fields</span>\nkv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"a12345\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>id<span class=\"token operator\">:</span> <span class=\"token string\">\"a12345\"</span><span class=\"token punctuation\">,</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"Craig\"</span><span class=\"token punctuation\">,</span> role<span class=\"token operator\">:</span> <span class=\"token string\">\"admin\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// persist an environmental variable value</span>\nkv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"env\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"IS_PROD\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// persist a hit count by page and userId</span>\nkv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"hits\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"account.tsx\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"a12345\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token number\">254</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"kv-versionstamp\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#kv-versionstamp\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><a href=\"https://deno.com/manual@v1.34.0/runtime/kv/key_space#versionstamp\" rel=\"noopener noreferrer\">KV versionstamp</a></h3><p>Each time a new value is persisted to Deno KV, it is automatically given a 12-byte version value based on the timestamp when the record was saved. KV calls this a <code>versionstamp</code>. When a value is updated, a new <code>versionstamp</code> is created.</p>\n<p>A new <code>versionstamp</code> will always be 'larger' than the previous one such that the second modification of a record will always be greater than the first in a boolean comparison (versionstamp2 &gt; versionstamp1). This is important if you want to <a href=\"#tracking-a-records-history\">track and display a record's version history</a>.</p>\n<p>The <code>versionstamp</code> assures that transactions are done atomically thus making sure that affiliated relational records in different indexes are consistent. Atomic operations in Deno KV can be checked to make sure that the data is consistent between the last fetch (<code>get()</code>) and when the new data will be persisted. This check is done with the record's <code>versionstamp</code> (<a href=\"#transactions-with-atomic\">for details see below</a>).</p>\n<h2 id=\"crud-operations\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#crud-operations\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>CRUD operations</h2><p>The main CRUD (create, read, update &amp; delete) operations in KV are defined as methods on the <code>Deno.Kv</code> class: <code>set()</code>(create &amp; update), <code>get()</code> (read) and <code>delete()</code> (delete). All these operations are asynchronous so a call needs to be preceded with the <code>await</code> keyword.</p>\n<blockquote>\n<p>💡 Working code that demonstrates CRUD operations can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/crud.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>All CRUD operations on Deno KV start with a connection to the KV database which is done with a simple call to <code>Deno.openKv()</code>:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Open a KV connection returning a Deno.Kv instance</span>\n<span class=\"token keyword\">const</span> kv<span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span>Kv <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">openKv</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>We'll be using the <code>kv</code> KV connection object (<code>Deno.Kv</code> instance) throughout our examples below.</p>\n<h3 id=\"crud-data\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#crud-data\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>CRUD data</h3><p>It's best to create a TypeScript type or interface to represent data models. For instance, a user model will look something like this:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">interface</span> <span class=\"token class-name\">User</span> <span class=\"token punctuation\">{</span>\n  id<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  email<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  phone<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Adding relations to the model, such as address and phone numbers would use <code>Address</code> and <code>Phone</code> interfaces. Both of them would have a <code>userId</code> field that would allow the lookup of an address and phone number for a particular user. For simplicity, we will focus on the <code>User</code> model here.</p>\n<p>Deno KV CRUD operations for a <code>User</code> will persist data that often comes from an HTML registration form filled out by a user. In our case, we'll manually create a user with a unique id:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> <span class=\"token string\">\"1\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  id<span class=\"token operator\">:</span> userId<span class=\"token punctuation\">,</span>\n  name<span class=\"token operator\">:</span> <span class=\"token string\">\"John Doe\"</span><span class=\"token punctuation\">,</span>\n  email<span class=\"token operator\">:</span> <span class=\"token string\">\"john@doe.com\"</span><span class=\"token punctuation\">,</span>\n  phone<span class=\"token operator\">:</span> <span class=\"token string\">\"2071231234\"</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The user id can be hard coded like I did here, but it's best that it is generated  as a unique value. The <code>crypto</code> object built into the Web platform is an easy way to create a unique id:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> crypto<span class=\"token punctuation\">.</span><span class=\"token function\">randomUUID</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"create-set\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#create-set\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Create (<code>set()</code>)</h3><p>Lets connect to the KV data store and insert the <code>user</code> data which requires two arguments, a <code>Deno.KvKey</code> and a value whose type is the method's TypeScript generic parameter (<code>User</code> in this example):</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">set</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">.</span>ok <span class=\"token operator\">===</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">There was a problem persisting user </span><span class=\"token\"><span class=\"token punctuation\">${</span>user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></pre></div><p>In SQL database terms the data table would be called \"user\" and the user id would be the primary key.</p>\n<p>The return value of a <code>set()</code> call is a <code>Promise&lt;Deno.KvCommitResult&gt;</code>, The <code>KvCommitResult</code> object contains a boolean <code>ok</code> field and a <code>versionstamp</code> field. If the <code>set()</code> call fails, <code>ok</code> is set to <code>false</code>. The <code>versionstamp</code> would be the versionstamp of the persisted record.</p>\n<h3 id=\"read-get\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#read-get\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Read (<code>get()</code>)</h3><p>The <code>get()</code> method is used to obtain a single record from Deno KV. For example:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> foundRecord<span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span>KvEntryMaybe<span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">get</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// Get user value</span>\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> foundRecord<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">;</span></pre></div><p>A call to <code>get()</code> returns a <code>Deno.KvEntryMaybe</code> object containing <code>key</code>, <code>value</code> and <code>versionstamp</code> properties (the Maybe part of <code>KvEntryMaybe</code> means that the result's value and versionstamp may be null). The <code>key</code> will always be the key you used in the the <code>get()</code> call. The <code>value</code> and <code>timestamp</code> values are those found in the KV store associated with the <code>key</code>.</p>\n<p>Here's an example of an object returned from <code>get()</code>:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// ids obtained from a call to crypto.getRandomUUID()</span>\n<span class=\"token punctuation\">{</span><span class=\"token property\">\"key\"</span><span class=\"token operator\">:</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"users\"</span><span class=\"token punctuation\">,</span><span class=\"token string\">\"4f18bbe6-1e0a-483f-9b89-556be297191c\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token property\">\"value\"</span><span class=\"token operator\">:</span><span class=\"token punctuation\">{</span><span class=\"token property\">\"id\"</span><span class=\"token operator\">:</span><span class=\"token string\">\"4f18bbe6-1e0a-483f-9b89-556be297191c\"</span><span class=\"token punctuation\">,</span><span class=\"token property\">\"name\"</span><span class=\"token operator\">:</span><span class=\"token string\">\"John Doe\"</span><span class=\"token punctuation\">,</span><span class=\"token property\">\"email\"</span><span class=\"token operator\">:</span><span class=\"token string\">\"john@doe.com\"</span><span class=\"token punctuation\">,</span><span class=\"token property\">\"phone\"</span><span class=\"token operator\">:</span><span class=\"token string\">\"2071239876\"</span><span class=\"token punctuation\">,</span><span class=\"token property\">\"age\"</span><span class=\"token operator\">:</span><span class=\"token number\">35</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token property\">\"versionstamp\"</span><span class=\"token operator\">:</span><span class=\"token string\">\"0000000000001d960000\"</span><span class=\"token punctuation\">}</span></pre></div><blockquote>\n<p>💡 Notice that value of a <code>get()</code> call is found in the <code>value</code> property of the call's result. This can trip you up since you might expect that the <code>get()</code> call returns only the value. Also be aware that the return value is wrapped in a <code>Promise</code> so make sure you prefix the call with <code>await</code>.</p>\n</blockquote>\n<p>If you call <code>get()</code> with a <code>key</code> that is not in the KV store, you will get back an object with both <code>value</code> and <code>versionstamp</code> equal to <code>null</code>.</p>\n<h4 id=\"set-options\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#set-options\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><code>set()</code> Options</h4><p>The <code>get()</code> method has an optional second argument called <code>options</code>. The <code>options</code> argument contains one field <code>consistency</code>. which has two values <code>\"eventual\"</code> or <code>\"strong\"</code>(<a href=\"#data-consistency\">see discussion below for details</a>).</p>\n<p>When running Deno KV locally, the <code>consistency</code> value is not relevant since the KV store is local. It is relevant when an application using Deno KV runs in the cloud on Deno Deploy since KV store instances are remotely distributed (<a href=\"#deno-deploy-data-centers\">see below</a>).</p>\n<h3 id=\"update-set\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#update-set\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Update (<code>set()</code>)</h3><p>Updating KV data would also use the <code>set()</code> method as is done for inserts:</p>\n<div class=\"highlight\"><pre>user<span class=\"token punctuation\">.</span>phone <span class=\"token operator\">=</span> <span class=\"token string\">\"5182349876\"</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">set</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">.</span>ok <span class=\"token operator\">===</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">There was a problem persisting user </span><span class=\"token\"><span class=\"token punctuation\">${</span>user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The arguments and return value from an update is the same as a create/insert call to <code>set()</code>.</p>\n<h3 id=\"delete-delete\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#delete-delete\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Delete (<code>delete()</code>)</h3><p>Deleting a record with the <code>delete()</code> method which requires a key (<code>Deno.KvKey</code>) argument.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">delete</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The delete method returns a <code>Promise&lt;void&gt;</code> which resolves to an <code>undefined</code> value.</p>\n<p>It is recommended that the mutation methods <code>set()</code> and <code>delete()</code> are done in a transaction which will return a result with an <code>ok</code> property to indicate if the transaction succeeded (<code>ok: true</code>) or failed (<code>ok: false</code>). See the <a href=\"#transactions-with-atomic\">transaction section below for more details</a>.</p>\n<h2 id=\"reading-multiple-records-list--getmany\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#reading-multiple-records-list--getmany\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Reading multiple records (<code>list()</code> &amp; <code>getMany()</code>)</h2><p>Reading multiple records involves the use of <code>list()</code> and <code>getMany()</code>, two methods on <code>Deno.Kv</code>.</p>\n<h3 id=\"reading-a-list-from-kv-with-list\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#reading-a-list-from-kv-with-list\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Reading a list from KV with <code>list()</code></h3><p>The <code>list()</code> method obtains multiple records and produces an async iterator (<code>Deno.KvListIterator</code>). The easiest way to loop through this kind of iterator is to use a <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of\" rel=\"noopener noreferrer\"><code>for-of</code> loop with <code>await</code></a> loop. Here is a simple example:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// The iterator is returned from a call to list().</span>\n<span class=\"token comment\">// 'await' used with 'for' because the iterator is async</span>\n<span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> row <span class=\"token keyword\">of</span> iterator<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> row<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// do something with the user object</span>\n<span class=\"token punctuation\">}</span></pre></div><blockquote>\n<p>💡 Working code that dives deeply into the <code>list()</code> method can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/lists.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>. Nearly all the code files in the repo use <code>list()</code> in various ways.</p>\n</blockquote>\n<p>The <code>list()</code> method takes two arguments <code>selector</code> and <code>options</code>:</p>\n<h4 id=\"1-the-first-list-argument-selector\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#1-the-first-list-argument-selector\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>1. The first <code>list()</code> argument: <code>selector</code></h4><p>The <code>selector</code> argument is an object with three optional fields: <code>prefix</code>, <code>start</code> and <code>end</code>.</p>\n<p><strong>The <code>prefix</code> <code>selector</code> field</strong></p>\n<p>Unlike the key arguments for <code>set()</code>, <code>get</code> and <code>delete()</code>, CRUD methods, the <code>prefix</code> key is a key part subset. All the key parts are usually string literals.</p>\n<p>Let's say you just wanted a list of users or user admins, your <code>list()</code> call would look like this:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// Get and iterator with a list of users</span>\n<span class=\"token keyword\">const</span> iterUser <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">list</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// An iterator with a list of admins</span>\n<span class=\"token keyword\">const</span> iterAdmin <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">list</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"admin\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><blockquote>\n<p>💡 To avoid TypeScript errors, call <code>list()</code> with a generic parameter (<code>User</code> in the previous example).</p>\n</blockquote>\n<p>Besides its use in querying, index prefixes are important when data is stored in remote KV instances (like Deno Deploy) because indexes with the same prefix are persisted near each other which facilitates rapid retrieval.</p>\n<p><strong><code>Selector</code> fields <code>start</code> and <code>end</code></strong></p>\n<p>Besides <code>prefix</code>, <code>start</code> and <code>end</code> are <code>selector</code> field options. The <code>list()</code> method takes one or two of these fields. The first one can be either <code>prefix</code> or <code>start</code> and the second one can be either <code>start</code> or <code>end</code>.</p>\n<p>The <code>start</code> or <code>end</code> options of <code>list()</code> define a range of KV records you want to select. But to be able to use <code>start</code> or <code>end</code>, you need to understand how key ordering is done (see the <a href=\"#sorting-with-indexes\">sorting with indexes section below</a>).</p>\n<p>While key ordering is instrumental for sorting, with <code>start</code> and <code>end</code> the order is used to define the beginning and ending of a range of ordered keys returned in the result set.</p>\n<p>The <code>start</code> option starts with the first matching record while the <code>end</code> method includes all previous records, but not the record it points to.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> rows <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  start<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Dow\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  end<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Zozos\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> row <span class=\"token keyword\">of</span> rows<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> row<span class=\"token punctuation\">.</span>value <span class=\"token keyword\">as</span> User<span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><blockquote>\n<p>💡 In order for the <code>start</code> option to work correctly, you need to make sure that the index you are sorting with is unique. For instance if the \"user_by_name\" index shown in the example was created using only the last name and if there are records with the same last name, only one will be included in the results. To explore how this happens see <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/sort-dup.ts\" rel=\"noopener noreferrer\">this example code in the blog's repo</a>.</p>\n</blockquote>\n<p>You can pair either <code>start</code> or <code>end</code> with the <code>prefix</code> field. When <code>prefix</code> is paired with <code>start</code>, the end point is the last record in the index. Alternately, when <code>prefix</code> is paired with <code>end</code>, the start point is the first index record. Here is an example:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> rows <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// points to first record</span>\n  end<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Zozos\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// Loop through results</span>\n<span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> row <span class=\"token keyword\">of</span> rows<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> row<span class=\"token punctuation\">.</span>value <span class=\"token keyword\">as</span> User<span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><h4 id=\"2-the-second-list-argument-options\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2-the-second-list-argument-options\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2. The second <code>list()</code> argument: <code>options</code></h4><p>The <code>options</code> <code>list()</code> argument is an object with a collection of fields:</p>\n<ul>\n<li><code>limit: number</code> - limits the size of a <code>list()</code> result set.</li>\n<li><code>cursor: string</code> - the <code>cursor</code> to resume iteration. Recall the <code>list()</code> method returns a <code>Deno.KvListIterator</code> that contains a <code>cursor</code> field. This is important in the <a href=\"#pagination-with-list\">pagination use case</a>.</li>\n<li><code>reverse: boolean</code> - returns the list in reverse order<ul>\n<li><code>prefix</code>, <code>start</code> or <code>end</code> can be used with <code>reverse</code>.</li>\n</ul>\n</li>\n<li><code>batchSize: number</code> - the size of the batches in which the list operation is performed. The default is equal to the 'limit' value or 100</li>\n<li><code>consistency: Deno.KvConsistencyLevel</code> - the transactional consistency, either <code>\"strong\"</code> or <code>\"eventual\"</code>(see the <a href=\"#data-consistency\">consistency section below</a>).</li>\n</ul>\n<h4 id=\"pagination-with-list\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#pagination-with-list\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Pagination with <code>list()</code></h4><p>The key to iterating a list in paged groups is the fact that <code>list()</code> returns a <a href=\"https://deno.land/api@v1.34.2?s=Deno.KvListIterator&amp;unstable=\" rel=\"noopener noreferrer\"><code>Deno.KvListIterator</code></a>. That iterator has a <code>cursor</code> field and a <code>next()</code> method, a consequence of that fact that it implements the <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols\" rel=\"noopener noreferrer\">async iterator protocol</a>.</p>\n<p>To paginate, you pass the cursor from one <code>list()</code> call to the next via the iterator result. That result's <code>iterator.cursor</code> value becomes the value of the <code>cursor</code> option in the next <code>list()</code> call. Here's how it looks:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// First call to list() returns iterator</span>\n<span class=\"token keyword\">let</span> iterator <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">list</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_age\"</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>limit<span class=\"token operator\">:</span> <span class=\"token number\">25</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token comment\">// Second call sets the cursor using the iterator from the first call to set the second call's cursor</span>\niterator <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">list</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_age\"</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>limit<span class=\"token operator\">:</span> <span class=\"token number\">25</span><span class=\"token punctuation\">,</span> cursor<span class=\"token operator\">:</span> iterator<span class=\"token punctuation\">.</span>cursor<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>When you don't know the number of items in an index you are querying with <code>list()</code> (\"user_by_age\" in this case), you can't just call until you get a null iterator, because <code>list()</code> always returns a <code>KvListIterator</code> object even if there are no results that can be obtained from the iterator.</p>\n<p>Instead, you use the <code>KvListIterator.next()</code> method's <code>done</code> property. This is best shown with an example.</p>\n<p>First, start with a list of users:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// start with a User interface</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">User</span> <span class=\"token punctuation\">{</span>\n  id<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">;</span>\n  name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  age<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">const</span> users<span class=\"token operator\">:</span> User <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token comment\">/* Multiple User objects here */</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span></pre></div><p>Next, we'll create a function to get an iterator. It will be called each time we want to display another group of users on a page. The first call to this method will set the <code>cursor</code> argument to an empty string which is used to determine whether <code>cursor</code> will be part of the second <code>list()</code> argument.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Obtain a new User iterator</span>\n<span class=\"token keyword\">function</span> <span class=\"token\"><span class=\"token function\">getIterator</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">T</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>cursor<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">,</span> limit<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span>KvListIterator<span class=\"token operator\">&lt;</span><span class=\"token\">T</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> optionsArg <span class=\"token operator\">=</span> cursor <span class=\"token operator\">!==</span> <span class=\"token string\">\"\"</span> <span class=\"token operator\">?</span> <span class=\"token punctuation\">{</span> limit<span class=\"token punctuation\">,</span> cursor <span class=\"token punctuation\">}</span> <span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> limit <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n  <span class=\"token keyword\">const</span> iterator <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">list</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_age\"</span><span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> optionsArg<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> iterator<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The <code>processIterator&lt;User&gt;()</code> function pulls out the iterated data (users in this case) and returns them with the cursor to be used in the next call to <code>getIterator()</code>:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Called with `User` generic param below (item=User; items=User{})</span>\n<span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token\"><span class=\"token function\">processIterator</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">T</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n  iterator<span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span>KvListIterator<span class=\"token operator\">&lt;</span><span class=\"token\">T</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">{</span>cursor<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">,</span> items<span class=\"token operator\">:</span> <span class=\"token\">T</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> cursor <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> iter<span class=\"token punctuation\">.</span><span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> items <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>result<span class=\"token punctuation\">.</span>done<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    cursor <span class=\"token operator\">=</span> iterator<span class=\"token punctuation\">.</span>cursor<span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// result.value returns full KvEntry object</span>\n    <span class=\"token keyword\">const</span> item <span class=\"token operator\">=</span> result<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>value <span class=\"token keyword\">as</span> <span class=\"token\">T</span><span class=\"token punctuation\">;</span>\n    items<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> iter<span class=\"token punctuation\">.</span><span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>cursor<span class=\"token punctuation\">,</span> users<span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>While the previous functions are generic, <code>printUsers</code> is used to print out an array of <code>User</code> objects with the page number:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">function</span> <span class=\"token function\">printUsers</span><span class=\"token punctuation\">(</span>users<span class=\"token operator\">:</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> pageNum<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Page </span><span class=\"token\"><span class=\"token punctuation\">${</span>pageNum<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> u <span class=\"token keyword\">of</span> users<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token\"><span class=\"token punctuation\">${</span>u<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">}</span></span><span class=\"token string\"> </span><span class=\"token\"><span class=\"token punctuation\">${</span>u<span class=\"token punctuation\">.</span>age<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Finally, initiate the pagination, printing out user data to the console:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// print out users in batches of USERS_PER_PAGE</span>\n<span class=\"token keyword\">const</span> <span class=\"token\">USERS_PER_PAGE</span> <span class=\"token operator\">=</span> <span class=\"token number\">3</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// aka page size</span>\n<span class=\"token keyword\">let</span> pageNumber <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">let</span> cursor <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">let</span> iter <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">getIterator</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>cursor<span class=\"token punctuation\">,</span> <span class=\"token\">USERS_PER_PAGE</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">await</span> <span class=\"token function\">processIterator</span><span class=\"token punctuation\">(</span>iter<span class=\"token punctuation\">,</span> pageNumber<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token function\">printUsers</span><span class=\"token punctuation\">(</span>processedItems<span class=\"token punctuation\">.</span>items <span class=\"token keyword\">as</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> pageNum<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\npageNumber<span class=\"token operator\">++</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// point the cursor to the next batch of data</span>\ncursor <span class=\"token operator\">=</span> iter<span class=\"token punctuation\">.</span>cursor<span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// stop iteration when cursor is empty</span>\n<span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span>cursor <span class=\"token operator\">!==</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  iter <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">getIterator</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>cursor<span class=\"token punctuation\">,</span> <span class=\"token\">USERS_PER_PAGE</span><span class=\"token punctuation\">,</span> keyPart<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> iterRet <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token\"><span class=\"token function\">processIterator</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>iter<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  cursor <span class=\"token operator\">=</span> iterRet<span class=\"token punctuation\">.</span>cursor<span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// cast items to User[]</span>\n  <span class=\"token keyword\">const</span> items<span class=\"token operator\">:</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> iterRet<span class=\"token punctuation\">.</span>items <span class=\"token keyword\">as</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>items<span class=\"token punctuation\">.</span>length <span class=\"token operator\">&gt;</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">printUsers</span><span class=\"token punctuation\">(</span>items<span class=\"token punctuation\">,</span> pageNum<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  pageNum<span class=\"token operator\">++</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><blockquote>\n<p>💡 The example snippets taken from code that demonstrates <code>list()</code> pagination can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/pagination.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<h4 id=\"paginate-kv-results-in-a-webapp\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#paginate-kv-results-in-a-webapp\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Paginate KV results in a webapp</h4><p>Paginating webapp results uses a lot of the previous code including <code>getIterator</code> and <code>processIterator</code>. You will also need to keep track of the <code>cursor</code> and <code>pageNumber</code> between requests for new pages, passing them into the next URL invocation via params in 'Next Page' and 'Previous Page' links in the footer.</p>\n<p>This is a tough thing to explain in black and white without example code (and in the process prolonging this already-too-long post), so I'll leave this as an exercise for the reader. However, please stay tuned. I hope to cover this in detail in a future article.</p>\n<blockquote>\n<p>💡 At this point, I'm prototyping a <a href=\"https://dash.deno.com/playground/kvpagination-playground\" rel=\"noopener noreferrer\">Deno Deploy playground that demonstrates KV pagination</a>, but it's quite not ready for prime time yet.</p>\n</blockquote>\n<h3 id=\"combining-records-from-multiple-indexes-with-getmany\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#combining-records-from-multiple-indexes-with-getmany\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Combining records from multiple indexes with <code>getMany()</code></h3><p>The <code>getMany()</code> method provides the opportunity to do a number of <code>get()</code> calls to separate indexes in one operation. The method accepts an array of keys and returns an array of <code>Deno.KvEntryMaybe</code> records, objects that include a <code>key</code>, <code>value</code> and <code>timestamp</code> fields.</p>\n<blockquote>\n<p>💡 Working code that demonstrates the <code>getMany()</code> method can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/get-many.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>It is important to know that each one of the keys should return single <code>KvEntryMaybe</code> objects like they would in a <code>get()</code> call. So it follows that the number of keys in the <code>getMany()</code> argument will always equal the number of of results in the call return array.</p>\n<p>For example, suppose we have three environmental variables stored in three separate indexes. Here's what that would look like:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// keys for storing env variables in KV</span>\n<span class=\"token keyword\">const</span> githubSecretKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"env_var\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"GITHUB_ACCESS_KEY\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> googleSecretKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"env_var\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"GOOGLE_ACCESS_KEY\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> discordSecretKey<span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span>KvKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"env_var\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"DISCORD_ACCESS_KEY\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// store env vars in separate indexes</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>discordSecretKey<span class=\"token punctuation\">,</span> <span class=\"token string\">\"password1\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>githubSecretKey<span class=\"token punctuation\">,</span> <span class=\"token string\">\"password2\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>googleSecretKey<span class=\"token punctuation\">,</span> <span class=\"token string\">\"password3\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// get all env var entries in one call to separate indexes</span>\n<span class=\"token keyword\">const</span> envVars <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">getMany</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span>\n  githubSecretKey<span class=\"token punctuation\">,</span>\n  googleSecretKey<span class=\"token punctuation\">,</span>\n  discordSecretKey<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// the 'enVars' result array would contain these values:</span>\n<span class=\"token comment\">//</span>\n<span class=\"token comment\">// enVars[0] = {key: [\"env_var\", \"GITHUB_ACCESS_KEY\"], value: \"password2\", versionstamp: \"001234\" }</span>\n<span class=\"token comment\">// enVars[1] = {key: [\"env_var\", \"GOOGLE_ACCESS_KEY\"], value: \"password3\", versionstamp: \"001235\" }</span>\n<span class=\"token comment\">// enVars[2] = {key: [\"env_var\", \"DISCORD_ACCESS_KEY\"], value: \"password1\", versionstamp: \"001236\" }</span>\n</pre></div><p>The <code>getMany()</code> method takes a second argument, <code>options</code> which is optional. The <code>option</code> argument has one property <code>consistency</code> which can be \"strong\" or \"eventual\". See the <a href=\"#data-consistency\">data consistency section</a> for more details on this topic.</p>\n<h2 id=\"transactions-with-atomic\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#transactions-with-atomic\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Transactions with <code>atomic()</code></h2><p>The <code>atomic()</code> method on <code>Deno.Kv</code> is used to do a transaction with KV. It returns a <code>Deno.AtomicOperation</code> class instance. Transactional operations are chained to <code>atomic()</code>. The chain must be terminated with a call to <code>commit()</code> in order for the transactional operations to be completed and the data persisted.</p>\n<blockquote>\n<p>💡 Working code that demonstrates atomic transactions can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/atomic.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a></p>\n</blockquote>\n<h3 id=\"using-check-to-validate-transactions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-check-to-validate-transactions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using <code>check()</code> to validate transactions</h3><p>A transactional chain off of <code>atomic()</code> should first call the <code>check()</code> method for each persistent operation. The <code>check()</code> method ensures that the <code>versionstamp</code> of data already in the KV store matches the <code>versionstamp</code> of the data being persisted. This feature is called optimistic concurrency control and assures data consistency and integrity within the KV store.</p>\n<p>A <code>get()</code> call must be done prior to <code>check()</code> to provide the <code>key</code> and <code>versionstamp</code> arguments of <code>check()</code>. For a new data insert the <code>get()</code> call will return null for the <code>versionstamp</code> (and <code>value</code>). If <code>check()</code> fails, then the transaction will fail and the data will not be committed.</p>\n<p>After <code>check()</code>, a series of <code>set()</code> and/or <code>delete()</code> calls are done to persist or delete KV data based on the provided key. These two methods behave the same way they do when called outside an <code>atomic()</code> chain (<a href=\"#crud-operations\">see CRUD section above</a>).</p>\n<p>An example will clarify how <code>check()</code> is used. Here we are trying to persist a user object with a changed phone number.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token number\">123</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token comment\">// Make sure versionstamp has not changed after last get()..</span>\n  <span class=\"token comment\">// This method requires both a key and versionstamp</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> user<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> user<span class=\"token punctuation\">.</span>versionstamp<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token comment\">// update phone number</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token operator\">...</span>user<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">,</span> phone<span class=\"token operator\">:</span> <span class=\"token string\">\"2070987654\"</span>  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>If <code>check()</code> fails, then any persistent operation in the chain will be bypassed and the <code>commit()</code> call returns a <code>Deno.KvCommitError</code>.</p>\n<h3 id=\"using-set-and-delete-for-transactional-persistance\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-set-and-delete-for-transactional-persistance\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using <code>set()</code> and <code>delete()</code> for transactional persistance</h3><p>In the previous example, <code>set()</code> is used to create or update data in a transactional chain. It is a method of <code>Deno.AtomicOperation</code> and works the same way as <code>Deno.Kv.set()</code> taking a key and value as arguments. It persists the value in a KV store with the key as the primary key (the record also contains a <code>versionstamp</code>).</p>\n<p>Likewise, <code>delete()</code>, also an <code>Deno.AtomicOperation</code> method, works the same way as its <code>Deno.Kv</code> counterpart. It takes a key argument and is used to remove records from a Deno KV store.</p>\n<p>Both <code>set()</code> and <code>delete()</code> can be chained to <code>atomic()</code> multiple times, but there cannot be more than 10 writes in a single persistent chain.</p>\n<p>The full power of KV transactional persistence is revealed when you need to store related objects. For instance, if you have a user who has an address and phone numbers. You should start with Typescript types defining the model:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// userId added prior to persistence</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">User</span> <span class=\"token punctuation\">{</span>\n  id<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  email<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">Address</span> <span class=\"token punctuation\">{</span>\n  userId<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  street<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  city<span class=\"token operator\">:</span> <span class=\"token\">string</span>\n  state<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  country<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  postalCode<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">Phone</span> <span class=\"token punctuation\">{</span>\n  userId<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  cell<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  work<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  home<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Each one of the entities will be persisted into a separate KV index within an atomic transaction. Let's assume that this information has been collected from an HTML user-registration form and processed into <code>User</code>, <code>Address</code> and <code>Phone</code> model objects. Here's a function to insert or update that data within a KV transaction:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">function</span> <span class=\"token function\">persistUser</span><span class=\"token punctuation\">(</span>user<span class=\"token operator\">:</span> User<span class=\"token punctuation\">,</span> address<span class=\"token operator\">:</span> Address<span class=\"token punctuation\">,</span> phone<span class=\"token operator\">:</span> Phone<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>user<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> crypto<span class=\"token punctuation\">.</span><span class=\"token function\">randomUUID</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    address<span class=\"token punctuation\">.</span>userId <span class=\"token operator\">=</span> userId<span class=\"token punctuation\">;</span>\n    phone<span class=\"token punctuation\">.</span>userId <span class=\"token operator\">=</span> userId<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n  <span class=\"token comment\">// get current records</span>\n  <span class=\"token keyword\">const</span> userRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> addressRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"address\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> phoneRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"phone\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token comment\">// check that data has not changed</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> userRecord<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> userRecord<span class=\"token punctuation\">.</span>versionstamp<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> addressRecord<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> addressRecord<span class=\"token punctuation\">.</span>versionstamp<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> phoneRecord<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> phoneRecord<span class=\"token punctuation\">.</span>versionstamp<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>userKey<span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>addressKey<span class=\"token punctuation\">,</span> address<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>phoneKey<span class=\"token punctuation\">,</span> phone<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The return value of a <code>kv.atomic()....commit()</code> call chain is a <code>Promise&lt;Deno.KvCommitResult&gt;</code> if the transaction succeeds. That object contains two fields: <code>ok</code> and <code>versionstamp</code>. The <code>ok</code> value will be <code>true</code> in a successful transaction.</p>\n<h3 id=\"transaction-failures\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#transaction-failures\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Transaction failures</h3><p>If a KV transaction fails within an <code>atomic()</code> call chain either by a failed <code>check()</code> or another transactional error, a <code>Deno.KvCommitError</code> is returned. In that case, the transaction is not persisted. The <code>KvCommitError</code> object has one field <code>ok</code> that is set to <code>false</code> when a failure happens.</p>\n<p>Here's what happens if you try to insert data that has already been inserted causing <code>check()</code> to fail:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes that User 1 has already been added to the KV index</span>\n<span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// user id</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token comment\">// Initial insert has null versionstamp</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n    <span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">User </span><span class=\"token\"><span class=\"token punctuation\">${</span>id<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">.</span>ok <span class=\"token operator\">===</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Data insert failed for User </span><span class=\"token\"><span class=\"token punctuation\">${</span>id<span class=\"token punctuation\">}</span></span><span class=\"token string\"> because it already exists</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><h3 id=\"tracking-a-records-history\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#tracking-a-records-history\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Tracking a record's history</h3><p>The <code>versionstamp</code> returned in a successful <code>atomic()</code> transaction is the versionstamp given to all operations within the <code>atomic()</code> call chain. This can be used to construct a version history of a record. In order to do this, you need to persist the <code>versionstamp</code> to a separate index. Here's what that would look like:</p>\n<blockquote>\n<p>💡 Working code that demonstrates how to track a record's history can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/record-history.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> crypto<span class=\"token punctuation\">.</span><span class=\"token function\">randomUUID</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>id<span class=\"token operator\">:</span> userId<span class=\"token punctuation\">,</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"Joan Smith\"</span><span class=\"token punctuation\">,</span> email<span class=\"token operator\">:</span> <span class=\"token string\">\"jsmith@example.com\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>result<span class=\"token punctuation\">.</span>ok<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"token comment\">// add the result versionstamp to a separate \"user_versionstamp\" index with a timestamp</span>\n  <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user_versionstamp\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">,</span> result<span class=\"token punctuation\">.</span>versionstamp <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>version<span class=\"token operator\">:</span> result<span class=\"token punctuation\">.</span>versionstamp<span class=\"token punctuation\">,</span> date<span class=\"token operator\">:</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Date</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getTime</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Since the <code>versionstamp</code> is part of the key, records will be ordered by <code>versionstamp</code> for each user.</p>\n<p>You can then use <code>list()</code> to display the history of a particular user's record.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// display version in reverse chronological order</span>\n<span class=\"token keyword\">const</span> iter <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_versionstamp\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">{</span>reverse<span class=\"token operator\">:</span>  <span class=\"token boolean\">true</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> version <span class=\"token keyword\">of</span> iter<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// display to stdout here; I'm sure you can do better</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Version: </span><span class=\"token\"><span class=\"token punctuation\">${</span>version<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>version<span class=\"token punctuation\">}</span></span><span class=\"token string\"> Date: </span><span class=\"token\"><span class=\"token punctuation\">${</span>version<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>date<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><h2 id=\"secondary-indexes\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#secondary-indexes\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Secondary Indexes</h2><p>You usually begin a Deno KV implementation by creating a primary index. The primary index will contain a model identifier part and a part unique to the value being persisted, like a user id for a \"user\" model. In that case, a particular \"user\" record will be found using the id.</p>\n<blockquote>\n<p>💡 Working code that demonstrates how to create and use secondary indexes  can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/secondary-index.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<h3 id=\"creation-and-mutation\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#creation-and-mutation\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Creation and mutation</h3><p>A secondary index is used to find a KV record or multiple records beyond the find-by-id search you can do with a primary index.</p>\n<p>For instance, if you want to search for a user by an email address you could have an index that uses the email address as the search criteria. These indexes usually have a name part that describes the index such as \"user_by_email\".</p>\n<p>When you have secondary indexes, you need to create them in a transactional context with the primary index to maintain data consistency.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> userId <span class=\"token operator\">=</span> crypto<span class=\"token punctuation\">.</span><span class=\"token function\">randomUUID</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> user <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>id<span class=\"token operator\">:</span> userId<span class=\"token punctuation\">,</span> name<span class=\"token operator\">:</span><span class=\"token string\">\"Joan Smith\"</span><span class=\"token punctuation\">,</span> email<span class=\"token operator\">:</span> <span class=\"token string\">\"joan.smith@example.com\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> userKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> userEmailLookupKey <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_email\"</span><span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">.</span>email<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">const</span> result <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> userKey<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">check</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>key<span class=\"token operator\">:</span> userEmailLookupKey<span class=\"token punctuation\">,</span> versionstamp<span class=\"token operator\">:</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>userKey<span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">)</span> <span class=\"token comment\">// primary index</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>userEmailLookup<span class=\"token punctuation\">,</span> user<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span> <span class=\"token comment\">// secondary index</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>result<span class=\"token punctuation\">.</span>ok<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Problem persisting user &amp; user_by_email\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>A <code>get()</code> call to \"user_by_email\" index returns the user's id. This will be used to find the record in the \"user\" primary index.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// The \"user_by_email\" record's value is the userId (see previous code)</span>\n<span class=\"token keyword\">const</span> userByEmailRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">get</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_email\"</span><span class=\"token punctuation\">,</span> userEmail<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// The value is the user Id</span>\n<span class=\"token keyword\">const</span> userByIdRecord <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">get</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>User<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> userByEmailRecord<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> user<span class=\"token operator\">:</span> User <span class=\"token operator\">=</span> userByIdRecord<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">;</span></pre></div><p>When the data is updated or deleted, you need to do that for both primary and secondary indexes</p>\n<p>Secondary indexes can be created with multiple lookup criteria such name and id. In a case like this, you should include the id because multiple users could have the same name. Here's an example:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// A secondary index by name and id</span>\nkv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"John Smith\"</span><span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>id<span class=\"token operator\">:</span> <span class=\"token number\">1</span><span class=\"token punctuation\">,</span> name<span class=\"token operator\">:</span> <span class=\"token string\">\"John Smith\"</span><span class=\"token punctuation\">,</span> email<span class=\"token operator\">:</span> <span class=\"token string\">\"jsmith@example.com\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></pre></div><p>A secondary index can also be structured to store records by a group or category. For instance you might want to group football/soccer players by their position on the pitch. That might look like this:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">type</span> <span class=\"token class-name\">Position</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"Goalkeeper\"</span> <span class=\"token operator\">|</span> <span class=\"token string\">\"Defender\"</span> <span class=\"token operator\">|</span> <span class=\"token string\">\"Midfielder\"</span> <span class=\"token operator\">|</span> <span class=\"token string\">\"Forward\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">Player</span> <span class=\"token punctuation\">{</span>\n  id<span class=\"token operator\">?</span><span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// id added later</span>\n  name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  position<span class=\"token operator\">:</span> Position<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">const</span> players<span class=\"token operator\">:</span> Player<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token comment\">/* player data goes here */</span><span class=\"token punctuation\">]</span>\n<span class=\"token comment\">// persist players to KV</span>\n<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> player <span class=\"token keyword\">of</span> players<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  player<span class=\"token punctuation\">.</span>id <span class=\"token operator\">=</span> crypto<span class=\"token punctuation\">.</span><span class=\"token function\">RandomUUID</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n  <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token comment\">// skip check() calls here;</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"players\"</span><span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"players_by_position\"</span><span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">.</span>position<span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> player<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// lookup players by a position</span>\n<span class=\"token keyword\">const</span> <span class=\"token function\">findPlayersByPosition</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>position<span class=\"token operator\">:</span> Position<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> iter <span class=\"token operator\">=</span>  kv<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"players_by_position\"</span><span class=\"token punctuation\">,</span> position<span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Players in </span><span class=\"token\"><span class=\"token punctuation\">${</span>position<span class=\"token punctuation\">}</span></span><span class=\"token string\"> position:</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> player <span class=\"token keyword\">of</span> iter<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> playerPosition <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">get</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>Player<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span>\n      <span class=\"token string\">\"players_by_position\"</span><span class=\"token punctuation\">,</span>\n      player<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>position<span class=\"token punctuation\">,</span>\n      player<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>id <span class=\"token operator\">??</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>playerPosition<span class=\"token punctuation\">.</span>value<span class=\"token operator\">?.</span>name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// Lookup midfielders</span>\n<span class=\"token keyword\">await</span> <span class=\"token function\">findPlayersByPosition</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Midfielder\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"sorting-with-indexes\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#sorting-with-indexes\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Sorting with indexes</h3><p>In KV, key parts are ordered lexicographically (roughly dictionary order) by their type, and within a given type, they are ordered by their value. Type ordering follows that <code>Uint8Array</code> &gt; <code>string</code> &gt; <code>number</code> &gt; <code>bigint</code> &gt; <code>boolean</code>. Within each type, there is a <a href=\"https://deno.com/manual@main/runtime/kv/key_space#key-part-ordering\" rel=\"noopener noreferrer\">defined ordering</a> too.</p>\n<blockquote>\n<p>💡 Code that shows an example of index ordering can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/key-order.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>The significance of the key ordering is it can be used to sort values. You do that by creating an secondary index using the desired sorting criteria as key parts.</p>\n<p>For instance, if you wanted to order user by their last name, you would have primary index and a secondary sorting index created like this:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// create indexes within a transaction</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token comment\">// check() calls omitted for brevity</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>user object<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// primary index</span>\n  <span class=\"token comment\">// sorting by name</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_name\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>lastName<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>firstName<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>user object<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>You'll noticed that I added <code>userId</code> to the secondary index. Otherwise duplicate records with the same first and last name will be ignored when the index is created. You could also use <code>email</code> (or another unique identifier) instead of <code>userId</code>.</p>\n<blockquote>\n<p>💡 A further exploration of duplicate record sorting behavior can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/sort-dup.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>To display the sorted values you use the <code>list()</code> method. If you want to sort the list in reverse order (largest value to smallest) set <code>reverse: true</code>. A good example of that is sorting by date with most recent dates ordered at the top:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// create user and user_create_date indexes</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token comment\">// check() calls omitted</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>user object<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span> <span class=\"token comment\">// primary index</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_create_date\"</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>createDate<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>userId<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token operator\">&lt;</span>user object<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// print out the index previously created in reverse chronological order</span>\n<span class=\"token keyword\">const</span> iter <span class=\"token operator\">=</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">list</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>prefix<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"user_by_create_date\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>reverse<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> user <span class=\"token keyword\">of</span> iter<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>user<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><blockquote>\n<p>💡 Working code that demonstrates KV sorting can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/sort.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<h2 id=\"math-operations-sum-min--max\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#math-operations-sum-min--max\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Math operations: <code>sum</code>, <code>min</code> &amp; <code>max</code></h2><p>There are three aggregate operations that can be used to keep track of a sum, a minimum and a maximum of a series of values that are stored in another index. A method exists for each one of the operations and a <code>mutate()</code> method that can alternatively used to collate those stats.</p>\n<blockquote>\n<p>💡 Working code that demonstrates the <code>min()</code>, <code>max()</code> and <code>sum()</code> methods can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/min-max-sum.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>All of these operations are <code>Deno.AtomicOperations</code> methods so they must be chained to <code>atomic()</code> with <code>commit()</code> as the chain terminator.</p>\n<h3 id=\"sum-min-and-max-methods\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#sum-min-and-max-methods\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><code>sum()</code>, <code>min()</code> and <code>max()</code> methods</h3><p>To keep track of aggregate sum, minimal and maximum values, you can use the <code>sum()</code>, <code>min()</code> and <code>max()</code> methods. All of these methods take a key and a value that is a <code>bigint</code> type. Here is an example</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">//shopping cart item</span>\n<span class=\"token keyword\">interface</span> <span class=\"token class-name\">CartItem</span> <span class=\"token punctuation\">{</span>\n  userId<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  itemDesc<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span>\n  price<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// cart data</span>\n<span class=\"token keyword\">const</span> cart<span class=\"token operator\">:</span> CartItem<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>\n  <span class=\"token punctuation\">{</span> userId<span class=\"token operator\">:</span> <span class=\"token string\">\"100\"</span><span class=\"token punctuation\">,</span> itemDesc<span class=\"token operator\">:</span> <span class=\"token string\">\"Arduino Uno kit\"</span><span class=\"token punctuation\">,</span> price<span class=\"token operator\">:</span> <span class=\"token number\">60</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span> userId<span class=\"token operator\">:</span> <span class=\"token string\">\"100\"</span><span class=\"token punctuation\">,</span> itemDesc<span class=\"token operator\">:</span> <span class=\"token string\">\"Temp sensor\"</span><span class=\"token punctuation\">,</span> price<span class=\"token operator\">:</span> <span class=\"token number\">10</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span> userId<span class=\"token operator\">:</span> <span class=\"token string\">\"100\"</span><span class=\"token punctuation\">,</span> itemDesc<span class=\"token operator\">:</span> <span class=\"token string\">\"Humidity sensor\"</span><span class=\"token punctuation\">,</span> price<span class=\"token operator\">:</span> <span class=\"token number\">15</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span> userId<span class=\"token operator\">:</span> <span class=\"token string\">\"100\"</span><span class=\"token punctuation\">,</span> itemDesc<span class=\"token operator\">:</span> <span class=\"token string\">\"Power cord with 5V regulator\"</span><span class=\"token punctuation\">,</span> price<span class=\"token operator\">:</span> <span class=\"token number\">18</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span> userId<span class=\"token operator\">:</span> <span class=\"token string\">\"100\"</span><span class=\"token punctuation\">,</span> itemDesc<span class=\"token operator\">:</span> <span class=\"token string\">\"Servo\"</span><span class=\"token punctuation\">,</span> price<span class=\"token operator\">:</span> <span class=\"token number\">8</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// add data to indexes</span>\n<span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> item <span class=\"token keyword\">of</span> cart<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n  kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart\"</span><span class=\"token punctuation\">,</span> item<span class=\"token punctuation\">.</span>userId<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> item<span class=\"token punctuation\">)</span> <span class=\"token comment\">// primary index</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">min</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_min\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token function\">BigInt</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">.</span>price<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">max</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_max\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token function\">BigInt</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">.</span>price<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">sum</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_sum\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token function\">BigInt</span><span class=\"token punctuation\">(</span>item<span class=\"token punctuation\">.</span>price<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// get stats</span>\n<span class=\"token keyword\">const</span> cartMin <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_min\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> cartMax <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_max\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">const</span> cartSum <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"cart_sum\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// print out cart stats</span>\n<span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Shopping cart data\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Min price: </span><span class=\"token\"><span class=\"token punctuation\">${</span><span class=\"token punctuation\">(</span>cartMin <span class=\"token keyword\">as</span> Deno<span class=\"token punctuation\">.</span>KvEntry<span class=\"token operator\">&lt;</span>bigint<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Max price: </span><span class=\"token\"><span class=\"token punctuation\">${</span><span class=\"token punctuation\">(</span>cartMax <span class=\"token keyword\">as</span> Deno<span class=\"token punctuation\">.</span>KvEntry<span class=\"token operator\">&lt;</span>bigint<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Total price: </span><span class=\"token\"><span class=\"token punctuation\">${</span><span class=\"token punctuation\">(</span>cartSum <span class=\"token keyword\">as</span> Deno<span class=\"token punctuation\">.</span>KvEntry<span class=\"token operator\">&lt;</span>bigint<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"mutate-method\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#mutate-method\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><code>mutate()</code> method</h3><p>The <code>mutate()</code> method is an alternate way to get aggregate stats. It takes an object with <code>type</code>, <code>key</code> and <code>value</code> properties. The type value can be either \"sum\", \"min\", or \"kax.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// keep track of website visits</span>\n<span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">atomic</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">mutate</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    type<span class=\"token operator\">:</span> <span class=\"token string\">\"sum\"</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// valid types are 'sum', 'min' &amp; 'max'</span>\n    key<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"hit_counter\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    value<span class=\"token operator\">:</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Deno</span><span class=\"token punctuation\">.</span><span class=\"token function\">KvU64</span><span class=\"token punctuation\">(</span><span class=\"token number\">1n</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">.</span><span class=\"token function\">commit</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>Deno.KvU64()</code> constructor function is a wrapper around an unsigned <code>bigint</code> value. The value is set via the constructor argument and is retrieved using the <code>value</code> field on a <code>Deno.KvU64()</code> instance. The <code>mutate()</code> value argument is always a <code>Deno.KvU64</code>.</p>\n<h2 id=\"using-kv-queue\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-kv-queue\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using KV Queue</h2><p>A queue was been added to Deno KV in Deno v1.34.3. If you recall from your data structures class (if you took one or picked it up on-th-fly like me), a queue is a linear sequence where operations are performed in first-in, first-out (FIFO) order. Enqueueing is the operation that adds items to a queue and dequeueing removes items from the queue.</p>\n<blockquote>\n<p>💡 Working code that demonstrates queueing can be found in the <a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/deno-kv/queue.ts\" rel=\"noopener noreferrer\">repo affiliated with this blog</a>.</p>\n</blockquote>\n<p>The KV queue implementation is done using the <code>Deno.Kv.enqueue()</code> and the <code>Deno.Kv.queueListen()</code> methods. The <code>enqueue()</code> method adds items to the database queue while the <code>queueListen()</code> method's callback function argument gets called when the item is dequeued.</p>\n<p>Besides being called as a standalone method, <code>enqueue()</code> can also  be chained to <code>Deno.Kv.atomic()</code> and be part of an atomic transaction.</p>\n<p>There are two arguments to <code>Deno.Kv.enqueue()</code>. The first one is the value to be queued, which is a <a href=\"https://deno.com/manual@v1.34.0/runtime/kv/key_space#values\" rel=\"noopener noreferrer\">valid KV value</a>. It returns a <code>Promise&lt;Deno.KvCommitResult&gt;</code> which resolves with a boolean <code>ok</code> value.</p>\n<p>The second <code>enqueue()</code> argument is optional. It is an object that contains two properties (both of them optional):</p>\n<ul>\n<li><code>delay</code> - the time in milliseconds to delay the delivery of an enqueued object. The default is zero.</li>\n<li><code>keysIfUndelivered</code> - an array of <code>Deno.Kv.Key[]</code> keys used to store a value if the value delivery to a queue listener is not successful after it has been retried several times.</li>\n</ul>\n<p>The <code>Deno.Kv.queueListen()</code> method has one argument which is a callback handler function. It takes an argument which is the queue value.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Assumes kv is a Deno.Kv object</span>\n<span class=\"token comment\">// Listen to enqueued objects</span>\nkv<span class=\"token punctuation\">.</span><span class=\"token function\">listenQueue</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>msg<span class=\"token operator\">:</span> <span class=\"token\">unknown</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Do a operation on the queued object</span>\n  <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span>msg<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">,</span> msg<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Value delivered: \"</span><span class=\"token punctuation\">,</span> msg<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">const</span> res <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> kv<span class=\"token punctuation\">.</span><span class=\"token function\">enqueue</span><span class=\"token punctuation\">(</span>\n  <span class=\"token punctuation\">{</span> key<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"test1\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> value<span class=\"token operator\">:</span> <span class=\"token string\">\"testing 1,2,3\"</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">{</span>\n    delay<span class=\"token operator\">:</span> <span class=\"token number\">1000</span><span class=\"token punctuation\">,</span> <span class=\"token comment\">// delay delivery for 1 second</span>\n    keysIfUndelivered<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"queue_failed\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"test1`\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Queue result: \"</span><span class=\"token punctuation\">,</span> res<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// Output:</span>\n<span class=\"token comment\">// Queue result:  { ok: true, versionstamp: \"0000000000001d100000\" }</span>\n<span class=\"token comment\">// Value delivered:  { key: [ \"test1\" ], value: \"testing 1,2,3\" }</span></pre></div><p>Like the <code>atomic()</code> method, the <code>enqueue()</code> method returns a result that contains <code>ok</code> and <code>versionstamp</code> fields. If the enqueueing fails, <code>ok</code> will be <code>false</code>; otherwise <code>true</code>.</p>\n<h2 id=\"kv-on-deno-deploy\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#kv-on-deno-deploy\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>KV on Deno Deploy</h2><p>The Deno runtime has an implementation of Deno KV using sqlite for persistence. This implementation is compatible with the Deno Deploy KV database (based on <a href=\"https://www.foundationdb.org/\" rel=\"noopener noreferrer\">Foundation DB</a>) so that code developed locally will seamlessly work when deployed to DD.</p>\n<p>If you do not have access to KV on Deno Deploy during its beta period, you can request it <a href=\"https://dash.deno.com/kv\" rel=\"noopener noreferrer\">here</a>.</p>\n<h3 id=\"deno-deploy-data-centers\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deno-deploy-data-centers\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deno Deploy Data Centers</h3><p>At this time Deno KV databases are replicated across at least 6 data centers, spanning 3 regions (US, Europe, and Asia).</p>\n<h4 id=\"data-consistency\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#data-consistency\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Data Consistency</h4><p>Data consistency refers to the assurance that all data centers maintain the same data even after new data is persisted.</p>\n<p>There are two kinds of data consistency in Deno Deploy. They can be configured using the <code>consistency</code> option when data is read from Deno KV. There are two options:</p>\n<ul>\n<li><p><code>consistency: \"strong\"</code> <em>(default)</em> - data reads from KV will come from the nearest region. Strong consistency implies:</p>\n<ul>\n<li><em>Serializability</em>: meaning that transactions are isolated at the highest level. It ensures that concurrent execution of multiple transactions results in a system state that would be the same as if the transactions were executed sequentially.</li>\n<li><em>Linearizability</em>: guarantees that read and write operations appear to be instantaneous and occur in real-time. Linearizability ensures a strong real-time ordering of operations.</li>\n</ul>\n</li>\n<li><p><code>consistency: \"eventual\"</code> - uses global replica's and caches for data reads and writes to maximize performance. In most cases the difference between strong and eventual consistency speed ranges from zero to about 100 milliseconds, which depends on how close the deployed app region is to North Virginia, USA which holds the primary write database for KV (at this point).</p>\n</li>\n</ul>\n<p>See the <a href=\"https://deno.com/deploy/docs/kv#consistency\" rel=\"noopener noreferrer\">Deno KV consistency section of the Deno Deploy docs</a> for more information.</p>\n<p>Data access is quicker with eventual consistency, but the data possibly will not be consistent between different replicated KV instances if a query is done shortly after one or more KV writes.</p>\n<p>For more details see <a href=\"https://deno.com/blog/kv#consistency-and-performance\" rel=\"noopener noreferrer\">https://deno.com/blog/kv#consistency-and-performance</a></p>\n<h4 id=\"synchronization-between-data-centers\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#synchronization-between-data-centers\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Synchronization Between Data Centers</h4><p>When data is written to a Deno KV store, the following things happen:</p>\n<ol>\n<li>The data is replicated synchronously to two data centers within the same Deno Deploy region.</li>\n<li>The data is replicated to the other data centers asynchronously.</li>\n</ol>\n<p>Deno Deploy docs state that the full asynchronous replication of data should occur withing 10 seconds.</p>\n<h4 id=\"loading-kv-data-into-deno-deploy\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#loading-kv-data-into-deno-deploy\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Loading KV data into Deno Deploy</h4><p>One issue with working with KV on Deno Deploy is how to seed the database before an app is started. The team recommends that you create an API route to handle the data loading and call the API route from a local command-line application sending the data with the command line calls.</p>\n<p>Loading of large data sets should be done using batch calls to the API to avoid overloading the system and minimize server CPU usage. In any case, you should make sure the API returns an OK (202) response if persistence succeeded or a failure response (500) enumerating which records were not persisted into KV.</p>\n<p>An alternative for a small amount of is to have the data loaded once when the application starts. This could be done in a <code>useEffect</code> hook with an empty array argument that would set an \"is_loaded\" index with a <code>true</code> value when the initial load is completed and have that value checked before loading to make sure that they are not loaded multiple times.</p>\n<h2 id=\"conclusions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusions</h2><p>Deno KV is not finished, so you should expect it to evolve. Here are some expectations:</p>\n<ul>\n<li>Stabilization of the KV API</li>\n<li>More KV features and abstractions built on it.</li>\n<li>Mature tools to aid its use as a database and to view and edit KV data stores.</li>\n</ul>\n<p>It remains to be seen whether these items will come from the Deno team or outside contributors.</p>\n<p>Since the technology is young, there is still some hesitancy to use KV. Another issue holding people back from using it is that the only deployment option is Deno Deploy at this time. KV pricing has not been set and whether the price will be based on Deno Deploy storage and/or throughput. Currently it is free to use locally and on Deploy.</p>\n<p>The fact that the mental model of KV is different from a relational database is also a drawback for some. KV has no tools like SQL available for easy persistence and querying.</p>\n<p>Still, KV has generated a lot of interest within the Deno community and there are a number of <a href=\"#apps-that-use-deno-kv\">app examples</a> and <a href=\"#kv-tools-under-development\">tools under development</a>.</p>\n<p>Application content data is not the only thing you could store in KV. Other examples are configuration data, logical flags and system-wide properties. Its use is only a matter of a developer's imagination.</p>\n<p>If this article piques your interest in Deno KV,  make sure you check out the <a href=\"#deno-manual-and-api-docs\">Deno Manual and API docs as outlined below</a>. The best place to stay on top of recent news on Deno KV development and application is the <strong>kv</strong> channel on the <a href=\"https://discord.gg/deno\" rel=\"noopener noreferrer\">Deno Discord instance</a>.</p>\n<p>Finally, check out <a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/deno-kv\" rel=\"noopener noreferrer\">the code affiliated with this post</a> for simple command-line examples of the things detailed in this article.</p>\n<hr />\n<h1 id=\"acknowledgements\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#acknowledgements\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Acknowledgements</h1><p><em>The author would like to thank members on the kv channel of the Deno Discord server for their direct and indirect help. In particular, I would like to point out the aid from N.D. Hrones, Andreu Botella, Lino Le Van and Heyang Zhou. Thank you.</em></p>\n<h1 id=\"appendix\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#appendix\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Appendix</h1><h2 id=\"references\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#references\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>References</h2><h3 id=\"deno-manual-and-api-docs\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deno-manual-and-api-docs\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deno Manual and API Docs</h3><ul>\n<li><strong>Deno KV Concepts and Patterns</strong> covered in the <a href=\"https://deno.com/manual@v1.33.1/runtime/kv\" rel=\"noopener noreferrer\">Deno Manual</a> details Deno KV's various concepts and patterns:<ul>\n<li><a href=\"https://deno.com/manual@main/runtime/kv/key_space\" rel=\"noopener noreferrer\">Key Space</a> is an overview of the keys, values and the versioning of the key-value records.</li>\n<li><a href=\"https://deno.com/manual@main/runtime/kv/operations\" rel=\"noopener noreferrer\">KV Operations</a> covers KV methods.</li>\n<li><a href=\"https://deno.com/manual@main/runtime/kv/transactions\" rel=\"noopener noreferrer\">Deno KV Transactions</a> shows how atomic transactions work in Deno KV.</li>\n<li><a href=\"https://deno.com/manual@main/runtime/kv/secondary_indexes\" rel=\"noopener noreferrer\">Deno KV Secondary Indexes</a> demonstrates how to create a secondary key to be used to query data in the key-value store outside of using the unique, primary-key-like id.</li>\n</ul>\n</li>\n<li><strong>The Deno KV API</strong> is covered in the <a href=\"https://deno.land/api@v1.33.1?unstable=&amp;s=Deno.Kv\" rel=\"noopener noreferrer\">API documentation</a> highlighting the various public classes, interfaces, methods and variables in the API.</li>\n<li><strong>Deno Deploy</strong> the KV implementation on DD is explained on the <a href=\"https://deno.com/deploy/docs/kv\" rel=\"noopener noreferrer\">Deno Deploy KV page</a>. It shows how to work with Deno KV on Deno Deploy and how the Deno KV data store is distributed and synchronized in various regions.</li>\n</ul>\n<h3 id=\"apps-that-use-deno-kv\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#apps-that-use-deno-kv\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Apps that use Deno KV</h3><ul>\n<li><a href=\"https://github.com/denoland/saaskit\" rel=\"noopener noreferrer\">SaasKit</a> - A boilerplate for creating SAAS applications using Fresh, OAuth, Stripe and Deno KV.</li>\n<li><a href=\"https://github.com/hashrock/kv-sketchbook\" rel=\"noopener noreferrer\">kv-sketchbook</a> - a 'dead simple' sketchbook app using Deno Fresh and KV.</li>\n<li><a href=\"https://github.com/denoland/tic-tac-toe\" rel=\"noopener noreferrer\">tic-tac-toe</a> - the classic game built with Deno Fresh and KV.\n(<a href=\"https://discord.com/channels/684898665143206084/1108074003018551327/1110625440948830238\" rel=\"noopener noreferrer\">https://discord.com/channels/684898665143206084/1108074003018551327/1110625440948830238</a>)</li>\n<li><a href=\"https://github.com/hashrock/kv-notepad\" rel=\"noopener noreferrer\">kv-notepad</a> - multiversion of a classic notepad app built with Deno Fresh and KV.</li>\n<li><a href=\"https://github.com/denoland/pixelpage\" rel=\"noopener noreferrer\">PixelPage</a> - a shared pixel art canvas build with Deno Fresh and KV.</li>\n<li><a href=\"https://github.com/denoland/showcase_todo\" rel=\"noopener noreferrer\">Todo List</a> - collaborative todo list build with Deno Fresh and KV.</li>\n<li><a href=\"https://github.com/denoland/fresh-kv-demo\" rel=\"noopener noreferrer\">fresh-kv-demo</a> - a boilerplate that uses Fresh and Deno KV for persistence.</li>\n<li><a href=\"https://github.com/KevinBatdorf/beats-kv-demo\" rel=\"noopener noreferrer\">Multiplayer KV Beats</a> - a multi-player beat box machine using Deno KV.</li>\n<li><a href=\"https://github.com/Wave-Studio/Reddino\" rel=\"noopener noreferrer\">Reddino</a> - A Reddit clone using Deno KV</li>\n<li><a href=\"https://github.com/the-abe-train/stone-bone-cone\" rel=\"noopener noreferrer\">Stone, Bone, Cone</a> - a Deno KV powered take on the Rock, Paper, Scissors game</li>\n<li><a href=\"https://github.com/denoland/deno-kv-hackathon/issues\" rel=\"noopener noreferrer\">Deno KV Hackathon submissions</a> - final entries for a recent hackathon to create apps and libraries that use Deno KV.<h3 id=\"kv-tools-under-development\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#kv-tools-under-development\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>KV Tools under development</h3></li>\n</ul>\n<p>Deno KV is in its infancy, so there are no mature tools for working with it. The following is a list of some promising utilities to use with KV:</p>\n<ul>\n<li><a href=\"https://github.com/skoshx/pentagon\" rel=\"noopener noreferrer\">Pentagon</a> - a <a href=\"https://www.prisma.io/\" rel=\"noopener noreferrer\">Prisma</a>-like ORM built on top of Deno KV</li>\n<li><a href=\"https://github.com/denoland/kv_api\" rel=\"noopener noreferrer\">kv_api</a> - a WIP demonstration of a REST-like API for invoking Deno KV operations.</li>\n<li><a href=\"https://github.com/Kycermann/deno-kv-plus\" rel=\"noopener noreferrer\">deno-kv-plus</a> - for building safe atomic transactions (see <a href=\"https://mieszko.xyz/deno-kv-plus\" rel=\"noopener noreferrer\">https://mieszko.xyz/deno-kv-plus</a>)</li>\n<li><a href=\"https://github.com/oliver-oloughlin/kvdex\" rel=\"noopener noreferrer\">kvdex</a> - a database wrapper for the Deno KV store.</li>\n<li><a href=\"https://github.com/lino-levan/otama\" rel=\"noopener noreferrer\">Otama</a> - exposes a simplified KV API.</li>\n<li><a href=\"https://github.com/hugojosefson/deno-kv-entity\" rel=\"noopener noreferrer\">kv_entity</a> - a typed library for specifying and storing entities in a Deno.Kv database.</li>\n<li><a href=\"https://github.com/vwkd/graphql-denokv\" rel=\"noopener noreferrer\">graphql-denokv</a> - GraphQL bindings for Deno KV.</li>\n</ul>\n",
            "url": "https://deno-blog.com/A_Comprehensive_Guide_to_Deno_KV.2023-06-30",
            "title": "A Comprehensive Guide to Deno KV",
            "summary": "A Comprehensive Guide to Deno KV",
            "date_modified": "2023-06-30T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/What_every_developer_should_know_about_the_Deno_third_party_module_registry.2023-04-09",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2023-04-09\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2023-04-09\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2023-04-09</h4><h5 id=\"10-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#10-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>10 min read</em></h5><h1 id=\"what-every-developer-should-know-about-the-deno-third-party-module-registry\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#what-every-developer-should-know-about-the-deno-third-party-module-registry\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>What every developer should know about the Deno third party module registry</h1><p>The Deno third-party registry is a place for Deno developers to publish their Deno-compatible ESM modules. It is in essence the Deno equivalent of the npm package manager.</p>\n<p>The data in the third-party registry is used as content for the <a href=\"https://deno.land/x\" rel=\"noopener noreferrer\">Deno Third Party Modules page</a>. Each module on that page's list links to a page providing module details including documentation, version information and source code.</p>\n<p>Both developers who create Deno libraries and users of those libraries should know how the third-party registry works and this post will fill in that gap.</p>\n<h2 id=\"how-the-third-party-registry-is-populated-and-organized\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#how-the-third-party-registry-is-populated-and-organized\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>How the third-party registry is populated and organized</h2><p>Registered modules can be accessed under the <code>https://deno.land/x/</code> URL for ESM imports, so, for instance, the Fresh web framework would be accessed using the <code>https://deno.land/x/fresh</code> URL. Module authors are urged to published new versions of their module to a sequentially numbered tagged branch (most authors use <a href=\"https://semver.org/spec/v2.0.0.html\" rel=\"noopener noreferrer\">semantic version or semver</a> numbering). In that case, the version number would be added to the end of the URL (e.g. <code>https://deno.land/x/fresh@1.1.4</code>).</p>\n<p>The original module list was ranked by Github stars, but it was discovered that a lot of the highest ranked entries were npm modules that did not work in Deno using an <code>https://deno.land/x/</code> import URL. A change to that ranking was first <a href=\"https://github.com/denoland/dotland/issues/2133\" rel=\"noopener noreferrer\">proposed by then Deno team member Kitson Kelly</a> in May 2022. He suggested a sorting algorithm based on metrics of popularity, quality and maintenance (see the proposal for details).</p>\n<p>The first implementation of the new ranking algorithm -- deployed in early October 2022 -- uses popularity only. This metric was produced using Google Analytics to track the number of requests to a given third-party library import URL, so that a popularity score of 100 means that 100 requests were made to that module over 30 days. There are no immediate plans to update the ranking algorithm to include quality and maintenance metrics.</p>\n<h2 id=\"important-things-to-keep-in-mind-before-registering-a-third-party-module\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#important-things-to-keep-in-mind-before-registering-a-third-party-module\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Important things to keep in mind before registering a third-party module</h2><ul>\n<li><strong>Modules are immutable</strong></li>\n</ul>\n<p>Once a module is published in the third-party registry, it cannot be changed or deleted. This makes sure that anyone using a <code>https://deno.land/x/*</code> import URL can be assured that the module will always be there. Module Git tags are also immutable. Any code changes must me published as a new tag.</p>\n<ul>\n<li><strong>Module name squatting is not allowed</strong></li>\n</ul>\n<p>There is a warning on the third-party module page that name squatting will not be tolerated. It suggests that if a module has not been under active development, it can be taken over by another developer and invites a proposal to do so.</p>\n<ul>\n<li><strong>Module source code must be contained in a public Github repository</strong></li>\n</ul>\n<p>The third-party registry does not support private Github repositories or another Git provider at this point.</p>\n<ul>\n<li><strong>The registry uses TSDoc/JSDoc comments to display module symbols (variables, classes, functions, TS interfaces and type aliases)</strong></li>\n</ul>\n<p>When a module is published the source code in the module's repository is scanned. Each source code file is checked for TSDoc/JSDoc comments for public functions, classes and TypeScript interfaces. If found, the content of the comment is used to create module documentation.  If not found, only the signatures of public variables, classes, functions, TS interfaces and type aliases will be displayed with no additional documentation, so it is a good idea to make sure your public module exports are well-documented and include example usage in the TSDoc/JSDoc comments.</p>\n<ul>\n<li><strong>Module authors must self-register a module</strong></li>\n</ul>\n<p>Publishing a new third-party module is accomplished by clicking on the button on the third-party modules page labelled \"Publish a module\". When that is done, the \"Adding a module\" page will be displayed. It asks for a module name and optionally a subdirectory. It then instructs the module author on how to create a Github webhook so the module can be picked up by the third-party registry automation.</p>\n<h2 id=\"the-third-party-registry-api\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#the-third-party-registry-api\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>The Third Party Registry API</h2><p>The module list for the <a href=\"https://deno.land/x\" rel=\"noopener noreferrer\">Deno Third Party Modules</a> page was pulled from a database via an API. Last October, version 2 of the API was published which coencided with the unvieling of a new ranking algorithm used on the page.</p>\n<p>The third party API is hosted at <a href=\"https://apiland.deno.dev\" rel=\"noopener noreferrer\">https://apiland.deno.dev</a>. An <a href=\"https://apiland.deno.dev/~/spec\" rel=\"noopener noreferrer\">OpenAPI specification for the API</a> exists in additon to <a href=\"https://redocly.github.io/redoc/?url=https://apiland.deno.dev/~/spec\" rel=\"noopener noreferrer\">human-readable documentation for the new API spec</a>.</p>\n<p>The third-party API has endpoints for module details, module metrics and module documentation (pages). Each endpoint URL begins with <a href=\"https://apiland.deno.dev\" rel=\"noopener noreferrer\">https://apiland.deno.dev</a>.</p>\n<h3 id=\"module-endpoints\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#module-endpoints\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Module Endpoints</h3><p>The modules API provides basic information on every third-party Deno module.</p>\n<ul>\n<li><code>/v2/modules</code> - Provide a list of all modules in the registry (<a href=\"https://apiland.deno.dev/v2/modules\" rel=\"noopener noreferrer\">Link</a>).</li>\n</ul>\n<p>Here's what the results look like with data from one of the 5,800+ (at the time of this writing) modules shown:</p>\n<pre><code>{\n  \"items\":[ // items module array\n    {\n      \"latest_version\": \"v0.1.8\",\n      \"versions\": [\n        \"v0.1.8\",\n        \"v0.1.7\",\n        \"v0.1.6\",\n        \"v0.1.5\",\n        \"v0.1.4\",\n        \"v0.1.3\",\n        \"v0.1.2\",\n        \"v0.1.1\"\n      ],\n      \"name\": \"install\",\n      \"description\": \"Deno Binary Installer\",\n      \"star_count\": 898,\n      \"popularity_score\": 47981,\n      \"tags\": [\n        {\n          \"kind\": \"popularity\",\n          \"value\": \"top_1_percent\"\n        }\n      ]\n    }\n  // other module data here ...\n  ]\n}</code></pre><p>The following query parameters are available for this endpoint:</p>\n<ol>\n<li><strong>limit</strong>: number that limits the result set size (100 by default)</li>\n<li><strong>page</strong>: page number used for pagination</li>\n</ol>\n<p>So if you wanted to get a list of 10 results on the third page of the modules endpoint, your URL would be <a href=\"https://apiland.deno.dev/v2/modules?limit=10&amp;page=3\" rel=\"noopener noreferrer\">https://apiland.deno.dev/v2/modules?limit=10&amp;page=3</a>.</p>\n<p>Leo Kettmeir from the Deno teams says that you currently cannot request more than 1,000 records (or 100 rows of ten records each) using this endpoint.</p>\n<ul>\n<li><code>/v2/modules/:module</code> - Provide information about a specific module</li>\n</ul>\n<p>This data is the same as an individual record in the <code>v2/modules</code> endpoint. For instance, the <a href=\"https://apiland.deno.dev/v2/modules/fresh\" rel=\"noopener noreferrer\">data for the Fresh module</a> looks like this:</p>\n<pre><code>{\n  \"latest_version\": \"1.1.4\",\n  \"versions\": [\n    \"1.1.4\",\n    \"1.1.3\",\n    \"1.1.2\",\n    \"1.1.1\",\n    \"1.1.0\",\n    // ... other versions here\n    \"v1.0.0\"\n  ],\n  \"name\": \"fresh\",\n  \"description\": \"The next-gen web framework.\",\n  \"star_count\": 10133,\n  \"tags\": [{ \"kind\": \"popularity\", \"value\": \"top_1_percent\" }],\n  \"popularity_score\": 8499\n}</code></pre><ul>\n<li><code>/v2/modules/:module/:version</code> - Provide information about a specific module version (<a href=\"https://apiland.deno.dev/v2/modules/fresh/1.1.4\" rel=\"noopener noreferrer\">Link for Fresh version 1.1.4</a>).</li>\n</ul>\n<p>Here's what the data looks like for Fresh version 1.1.4:</p>\n<pre><code>{\n  \"upload_options\": {\n    \"ref\": \"1.1.4\",\n    \"type\": \"github\",\n    \"repository\": \"denoland/fresh\"\n  },\n  \"uploaded_at\": \"2023-03-08T09:48:46.820Z\",\n  \"analysis_version\": \"1\",\n  \"name\": \"fresh\",\n  \"description\": \"The next-gen web framework.\",\n  \"version\": \"1.1.4\"\n}</code></pre><h3 id=\"module-metric-endpoints\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#module-metric-endpoints\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Module Metric Endpoints</h3><ul>\n<li><code>/v2/metrics/modules</code> - All module's metrics (<a href=\"https://apiland.deno.dev/v2/metrics/modules\" rel=\"noopener noreferrer\">Link</a>)</li>\n</ul>\n<p>The \"metrics\" returned by this endpoint is much more than just metrics. Then include basic data on the module including dependencies, versions and <strong>uploaded_at</strong> datetime when a new module version github tag was created.</p>\n<p>This endpoint supports the query parameters <strong>limit</strong> and <strong>page</strong> that is used to determine result set size and page number like the modules endpoint.</p>\n<p>If also supports an <strong>order_by</strong> query parameter which ranks the query results by a particular field value.</p>\n<p>The results set for this endpoint puts data in an <strong>items</strong> array like was done for the modules endpoint above.</p>\n<ul>\n<li><code>/v2/metrics/modules/:module</code> - Metrics for a specific third-party module</li>\n</ul>\n<p>Here's what metrics for the Deno web framework <a href=\"https://ultrajs.dev\" rel=\"noopener noreferrer\">Ultra</a> looks like:</p>\n<div class=\"highlight\"><pre><span class=\"token punctuation\">{</span>\n  <span class=\"token property\">\"metrics\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">\"popularity\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"prev_sessions_30_day\"</span><span class=\"token operator\">:</span> <span class=\"token number\">256</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"score\"</span><span class=\"token operator\">:</span> <span class=\"token number\">241</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"prev_score\"</span><span class=\"token operator\">:</span> <span class=\"token number\">266</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"sessions_30_day\"</span><span class=\"token operator\">:</span> <span class=\"token number\">233</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"prev_users_30_day\"</span><span class=\"token operator\">:</span> <span class=\"token number\">283</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"users_30_day\"</span><span class=\"token operator\">:</span> <span class=\"token number\">254</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"name\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"ultra\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"updated\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"2023-04-02T00:24:44.476Z\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"maintenance\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"quality\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token property\">\"info\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">\"kind\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"modinfo\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"module\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"ultra\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"description\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"Zero-Legacy Deno/React Suspense SSR Framework\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"readme\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"path\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"/README.md\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"size\"</span><span class=\"token operator\">:</span> <span class=\"token number\">4086</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"type\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"file\"</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"version\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v2.2.1\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"tags\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"dependencies\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"color\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"3.3.2\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"crayon\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v2.5.1\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"hono\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v1.3.2\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"mesozoic\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v0.8.0\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"outdent\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"0.1.12\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"deno.land/x\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"wait\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"18.2.0\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"org\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"esm.sh\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"react-dom\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"18.2.0\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"esm.sh\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"org\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"react\"</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">{</span>\n        <span class=\"token property\">\"ver\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"0.176.0\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"src\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"std\"</span><span class=\"token punctuation\">,</span>\n        <span class=\"token property\">\"pkg\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"std\"</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"upload_options\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"ref\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v2.2.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"repository\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"exhibitionist-digital/ultra\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"type\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"github\"</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"latest_version\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"v2.2.1\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"dependency_errors\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"versions\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>\n      <span class=\"token string\">\"v2.2.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.2.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.7\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.7\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.6\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.5\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.4\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.3\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.2\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.1.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.0.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.0.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v2.0.0-beta.19\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token comment\">// ... other beta versions</span>\n      <span class=\"token string\">\"v2.0.0-alpha.19\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token comment\">// ... other alpha versions</span>\n      <span class=\"token string\">\"v1.0.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v1.0.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.8.2\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.8.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.8.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.6\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.5\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.4\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.3\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.2\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.7.0\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.6\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.5\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.4\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.3\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.2\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.1\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token string\">\"v0.0\"</span>\n    <span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"uploaded_at\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"2023-02-06T08:34:08.756Z\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token property\">\"config\"</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token property\">\"path\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"/deno.json\"</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"size\"</span><span class=\"token operator\">:</span> <span class=\"token number\">867</span><span class=\"token punctuation\">,</span>\n      <span class=\"token property\">\"type\"</span><span class=\"token operator\">:</span> <span class=\"token string\">\"file\"</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></pre></div><p>Under the <strong>popularity</strong> field, a \"session\" is defined by Google analytics as a visit to a page for 30 minutes or less.\nThe <strong>users</strong> field value also comes from GA.</p>\n<p>Note that there is are <strong>quality</strong> and <strong>maintenance</strong> metric fields which were part of the <a href=\"https://github.com/denoland/dotland/issues/2133\" rel=\"noopener noreferrer\">original page ranking proposal</a>, but they are unused right now.</p>\n<h3 id=\"documentation-page-endpoints\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#documentation-page-endpoints\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Documentation Page Endpoints</h3><p>The pages API focuses on data for the module documentation pages. Third party API routes that begin with <code>v2/pages</code> are used to display API documentation for a particular Deno module.</p>\n<ul>\n<li><code>/v2/pages/mod/doc/:module/:version/:path*</code> - Provides data to render a documentation page for a module</li>\n</ul>\n<p>For instance, to pull up the <code>types.ts</code> file documentation data, you would use the URL: <a href=\"https://apiland.deno.dev/v2/pages/mod/doc/std/0.182.0/testing/types.ts\" rel=\"noopener noreferrer\">https://apiland.deno.dev/v2/pages/mod/doc/std/0.182.0/testing/types.ts</a> which is the data that is <a href=\"https://deno.land/std@0.182.0/testing/types.ts\" rel=\"noopener noreferrer\">rendered in this page</a>.</p>\n<h3 id=\"example-use-of-the-third-party-api\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#example-use-of-the-third-party-api\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Example use of the third-party API</h3><p>Obviously, the third-module registry API provides a lot of data to work with. You could essentially create your own version of <code>https://deno.land/x</code>. This version could be supercharged with information in the API not found on the Deno Third Party Modules pages. Ranking the results by a different characteristic would be interesting or you could create your own popularity score algorithm.</p>\n<p>I have create a simple prototype that displays the Deno Third Party Modules page data showing the top 500 modules and adding the Github star count to each module record. <a href=\"https://3rd-party-api.deno.dev/\" rel=\"noopener noreferrer\">You can see it in action here</a>.</p>\n<p>This page includes a drop-down to sort the top 500 results by popularity score, Github star count or a combination of popularity score (weighted 75%) and GH star count (weighted 25%).</p>\n<p>This demo shows one of the many ways the API data could be displayed. Graphical renditions would be an interesting option, for instance.</p>\n<p>The code for my API demo is found at <a href=\"https://github.com/cdoremus/3rd-party-api\" rel=\"noopener noreferrer\">this github repository</a>.</p>\n<h2 id=\"conclusions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusions</h2><p>This article focused on the Deno third party modules list and the API used to create that list.</p>\n<p>The API was used to revise the way modules are sorted on the <a href=\"https://deno.land/x\" rel=\"noopener noreferrer\">Deno Third Party Modules page</a>. Still, this new ranking is just an initial implementation of the <a href=\"https://github.com/denoland/dotland/issues/2133\" rel=\"noopener noreferrer\">original algorithm  proposed by Kitson Kelly</a> who no longer works for Deno. If you are interested in improvements to the ranking system, you should post comments to that proposal.</p>\n<p>Finally, take a look at <a href=\"https://3rd-party-api.deno.dev/\" rel=\"noopener noreferrer\">my demo app that used the third party API</a> to show different module ranking views. There are numerous other ways to display and analyze data coming from the API and I urge you to use your imagination and technical skill to improve on my humble prototype.</p>\n<hr />\n<h2 id=\"acknowledgements\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#acknowledgements\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Acknowledgements</h2><p>The author would like to thank former Deno team member Kitson Kelly for answering some questions on the API last fall, and current Deno team member Leo Kettmeir for recently filling in my third-party module API knowledge gaps. Leo also reviewed this article before it was published and I thank him for his thoughtful comments.</p>\n<h2 id=\"references\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#references\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>References</h2><ul>\n<li>Kitson Kelly's June, 2022 talk introducing the new third-party API version he was working on: <a href=\"https://youtu.be/G_2AgdgEbkI?t=1554\" rel=\"noopener noreferrer\">https://youtu.be/G_2AgdgEbkI?t=1554</a></li>\n<li>Kitson Kelly's Github repo demonstrating the API shown in his June, 2022 talk: <a href=\"https://github.innominds.com/kitsonk/deno-on-the-edge\" rel=\"noopener noreferrer\">https://github.innominds.com/kitsonk/deno-on-the-edge</a></li>\n<li>Section of Leo Kettmeir's February, 2023 talk covering the third-party API: <a href=\"https://www.youtube.com/watch?v=q5wWK9blBKQ&amp;t=912s\" rel=\"noopener noreferrer\">https://www.youtube.com/watch?v=q5wWK9blBKQ&amp;t=912s</a></li>\n</ul>\n",
            "url": "https://deno-blog.com/What_every_developer_should_know_about_the_Deno_third_party_module_registry.2023-04-09",
            "title": "What every developer should know about the Deno third party module registry",
            "summary": "What every developer should know about the Deno third party module registry",
            "date_modified": "2023-04-09T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/Using_SQLite_with_Deno.2023-02-24",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2023-02-24\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2023-02-24\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2023-02-24</h4><h5 id=\"9-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#9-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>9 min read</em></h5><h1 id=\"using-the-database-sqlite-with-deno\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-the-database-sqlite-with-deno\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using the database SQLite with Deno</h1><p><a href=\"https://www.sqlite.org/index.html\" rel=\"noopener noreferrer\">SQLite</a> is a lightweight database available on most platforms. It is designed to exist in memory or persist to a local file system. This post will discuss how to interact with a SQLite database in Deno.</p>\n<p>At this point Deno-native[<a href=\"#1-deno-native\">1</a>] libraries are the only Deno SQLite client options since the <a href=\"https://github.com/denoland/deno/issues/15611\" rel=\"noopener noreferrer\">npm sqlite3 client does not work when run in Deno using the <code>npm:</code> prefix</a> because <a href=\"https://github.com/denoland/deno/issues/16164\" rel=\"noopener noreferrer\">postinstall scripts are not supported by Deno yet when importing with the <code>npm:</code> prefix</a> (it is on the roadmap).</p>\n<p>There are two Deno-native third-party libraries that are SQLite clients (aka drivers). Both of them work with SQLite version 3, the current version. They are <a href=\"https://deno.land/x/sqlite\" rel=\"noopener noreferrer\"><code>deno-sqlite</code></a> and <a href=\"https://deno.land/x/sqlite3\" rel=\"noopener noreferrer\"><code>sqlite3</code></a>.</p>\n<p>The <code>deno-sqlite</code> third-party library contains both a SQLite client and a SQLite implementation compiled as a Web Assembly Module (WASM). This allows SQLite to be used in a Deno program without need for an external SQLite engine. This library is named <code>sqlite</code> in the Deno third-party module registry while the Github repository is called <code>deno-sqlite</code> which is the name I will use in this post.</p>\n<p>The <code>sqlite3</code> library is just a SQLite client. But it is designed with performance in mind as it uses the Deno Foreign Function Interface (FFI) to allow access to native file reading and writing functionality rather than going through a JavaScript wrapper around native I/O access built into Deno.</p>\n<p>This post will explore how to do CRUD operations with each library using code snippets. There is a <a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/sqlite\" rel=\"noopener noreferrer\">corresponding repo folder</a> that contains full working examples of the code in this post.</p>\n<h2 id=\"creating-a-database-and-inserting-data\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#creating-a-database-and-inserting-data\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Creating a database and inserting data</h2><h4 id=\"database-creation-and-insertion-with-deno-sqlite\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#database-creation-and-insertion-with-deno-sqlite\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Database creation and insertion with deno-sqlite</h4><p>Persistence using <code>deno-sqlite</code> revolves around the <code>DB</code> class. The constructor can be used to point to a file-based database or one held in memory using the default constructor (or the <code>\":memory:\"</code> token as a constructor argument).</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> <span class=\"token\">DB</span> <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/sqlite@v3.7.0/mod.ts\"</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// Open a database to be held in memory</span>\n  <span class=\"token keyword\">const</span> db <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">DB</span><span class=\"token punctuation\">(</span><span class=\"token string\">\":memory:\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// or new DB()</span>\n  <span class=\"token comment\">// Use new DB(\"file.db\"); for a file-based database</span>\n  db<span class=\"token punctuation\">.</span><span class=\"token function\">execute</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">\n  CREATE TABLE IF NOT EXISTS people (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    name TEXT\n  )</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// Insert data within a transaction</span>\n  db<span class=\"token punctuation\">.</span><span class=\"token function\">transaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> name <span class=\"token keyword\">of</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"Peter Parker\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Clark Kent\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Bruce Wayne\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      db<span class=\"token punctuation\">.</span><span class=\"token function\">query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"INSERT INTO people (name) VALUES (?)\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// Todo: Other CRUD operations here...</span>\n\n  <span class=\"token comment\">// Close database to clean up resources</span>\n  db<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></pre></div><p>The <code>DB.transaction</code> method is used for transactional control. If the function argument throws an error, the transaction is rolled back; otherwise it is committed. This <code>transaction</code> method can also be used for updates and deletes.</p>\n<h4 id=\"database-creation-and-insertion-with-sqlite3\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#database-creation-and-insertion-with-sqlite3\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Database creation and insertion with sqlite3</h4><p>The <code>sqlite3</code> library uses a <code>Database</code> class to initiate persistence. It's constructor takes a file path or <code>\":memory:\"</code> token for an in-memory database. The constructor also takes an options argument with a number of fields that are detailed in the <a href=\"https://github.com/denodrivers/sqlite3/blob/main/doc.md\" rel=\"noopener noreferrer\">documentation</a>.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Database <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/sqlite3@0.8.0/mod.ts\"</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> db <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Database</span><span class=\"token punctuation\">(</span><span class=\"token string\">\":memory:\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// or a file name/path</span>\n  db<span class=\"token punctuation\">.</span><span class=\"token function\">exec</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">\n  CREATE TABLE IF NOT EXISTS people (\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\n    name TEXT\n  )</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// insert data in a transaction</span>\n  <span class=\"token keyword\">const</span> inserts <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token function\">transaction</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>data<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> name <span class=\"token keyword\">of</span> data<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      db<span class=\"token punctuation\">.</span><span class=\"token function\">exec</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"INSERT INTO people (name) VALUES (?)\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span>name<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">inserts</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token string\">\"Peter Parker\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Clark Kent\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Bruce Wayne\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// Todo: Other CRUD operations here...</span>\n\n  <span class=\"token comment\">// Close database to clean up resources</span>\n  db<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>Database.transaction</code> method is used to modify SQLite data in a transactional context. Unlike <code>deno-sqlite</code>, the <code>transaction</code> method returns a function that needs to be called with the SQL operation's data in order to run the transaction. But like <code>deno-sqlite</code>, the <code>sqlite3</code> transaction behavior depends on whether an error is thrown in the function (rollback) or it cleanly returns (commit).</p>\n<h2 id=\"running-queries\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#running-queries\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Running queries</h2><h4 id=\"querying-using-deno-sqlite\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#querying-using-deno-sqlite\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Querying using deno-sqlite</h4><p>The <code>deno-sqlite</code> lib has two ways of running a query. The first uses the <code>DB.query</code> method.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n  <span class=\"token keyword\">const</span> results <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">query</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">[</span><span class=\"token\">number</span><span class=\"token punctuation\">,</span> <span class=\"token\">string</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n    <span class=\"token string\">\"SELECT id, name FROM people\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">,</span> name<span class=\"token punctuation\">]</span> <span class=\"token keyword\">of</span> results<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token\"><span class=\"token punctuation\">${</span>id<span class=\"token punctuation\">}</span></span><span class=\"token string\">: </span><span class=\"token\"><span class=\"token punctuation\">${</span>name<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span></pre></div><p>The second way of running a query in <code>deno-sqlite</code> is with a prepared statement which uses the <code>DB.prepareQuery</code> method which returns an object that conforms to the <code>PreparedStatement</code> interface.</p>\n<div class=\"highlight\"><pre>  <span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n\n  <span class=\"token keyword\">const</span> query <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">prepareQuery</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">[</span><span class=\"token\">number</span><span class=\"token punctuation\">,</span> <span class=\"token\">string</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n    <span class=\"token string\">\"SELECT id, name FROM people\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> <span class=\"token punctuation\">[</span>id<span class=\"token punctuation\">,</span> name<span class=\"token punctuation\">]</span> <span class=\"token keyword\">of</span> query<span class=\"token punctuation\">.</span><span class=\"token function\">iter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token\"><span class=\"token punctuation\">${</span>id<span class=\"token punctuation\">}</span></span><span class=\"token string\">: </span><span class=\"token\"><span class=\"token punctuation\">${</span>name<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  query<span class=\"token punctuation\">.</span><span class=\"token function\">finalize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this example, <code>preparedQuery</code> returned the <code>query</code> object as a <code>PreparedQuery</code> interface type. There are three ways to bind parameters and get results from a Prepared query:</p>\n<ul>\n<li><p>The <code>iter</code> method binds the parameters to the query and returns an iterator over rows. Use this if there are multiple rows in a result set because it avoids loading all returned rows into memory at once allowing a large number of rows to be processed sequentially.</p>\n</li>\n<li><p>The <code>all</code> method binds the data and returns a full result set in an array.</p>\n</li>\n<li><p>The <code>first</code> method returns the first item of a result set returned by the query.</p>\n</li>\n</ul>\n<p>You also need to run the <code>finalize</code> method on the <code>PreparedQuery</code> or you will get an error message:</p>\n<pre><code>Uncaught SqliteError: unable to close due to unfinalized statements or unfinished backups\n      throw new SqliteError(this.#wasm);</code></pre><p>While you can dynamically create a SQL string and run a query on the resulting string using the <code>DB.query</code> method, it can easily cause a <a href=\"https://owasp.org/www-community/attacks/SQL_Injection\" rel=\"noopener noreferrer\">SQL Injection</a> attack on your database. To avoid that problem always use prepared statements with parameterized queries (i.e the <code>DB.prepareQuery</code> method).</p>\n<h4 id=\"querying-using-sqlite3\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#querying-using-sqlite3\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Querying using sqlite3</h4><p>The <code>sqlite3</code> library only uses prepared statement to do queries. A <code>Statement</code> object is returned from the call to <code>Database.prepare</code>.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n\n  <span class=\"token comment\">// Create a prepared statement</span>\n  <span class=\"token keyword\">const</span> stmt <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token function\">prepare</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT id, name FROM people where id=:id\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Bind the parameter to the statement</span>\n  <span class=\"token keyword\">const</span> row <span class=\"token operator\">=</span> <span class=\"token function\">stmt</span><span class=\"token punctuation\">.</span><span class=\"token function\">bind</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> id<span class=\"token operator\">:</span> <span class=\"token number\">1</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Row for id 1: </span><span class=\"token string\">`</span></span><span class=\"token punctuation\">,</span> row<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  stmt<span class=\"token punctuation\">.</span><span class=\"token function\">finalize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// not required, otherwise finalization is automatic</span></pre></div><p>The <code>Statement.bind</code> method is one of may ways to bind parameters to prepared statements. Other <code>Statement</code> methods used to bind data are</p>\n<ul>\n<li><code>all</code> - Run the query and return the resulting rows in objects with column name mapped to their corresponding values.</li>\n<li><code>values</code> - Run the query and return the resulting rows where rows are array of columns.</li>\n<li><code>run</code> - Run the query with it returning the number of rows in the result set. To get the resulting rows, you must then call <code>Statement.get()</code> with the row number (starting with 1) to get the individual data rows.</li>\n</ul>\n<h2 id=\"updating-data\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#updating-data\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Updating data</h2><p>Updating the database with both SQLite libraries uses prepared statements (i.e. the same methods) like when running a query (<code>db.prepareQuery</code> or <code>Database.prepare</code>).</p>\n<h4 id=\"updating-using-deno-sqlite\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#updating-using-deno-sqlite\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Updating using deno-sqlite</h4><div class=\"highlight\"><pre>  <span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n  <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> newName <span class=\"token operator\">=</span> <span class=\"token string\">\"Wade Winston Wilson\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> query <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">prepareQuery</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">[</span><span class=\"token\">number</span><span class=\"token punctuation\">,</span> <span class=\"token\">string</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span> id<span class=\"token operator\">:</span> <span class=\"token\">number</span> <span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n      <span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">UPDATE people set name=? where id=:id</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    query<span class=\"token punctuation\">.</span><span class=\"token function\">all</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span>newName<span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    query<span class=\"token punctuation\">.</span><span class=\"token function\">finalize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Todo: Verify that data has been updated and close the database</span></pre></div><p>As with querying, a <code>PreparedStatement</code> object needs to be finalized when the update has been completed or a <code>SqliteError</code> will be thrown.</p>\n<h4 id=\"updating-using-sqlite3\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#updating-using-sqlite3\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Updating using sqlite3</h4><div class=\"highlight\"><pre>  <span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n  <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> newName <span class=\"token operator\">=</span> <span class=\"token string\">\"Wade Winston Wilson\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> stmt <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token function\">prepare</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">UPDATE people set name=? where id=?</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  stmt<span class=\"token punctuation\">.</span><span class=\"token function\">run</span><span class=\"token punctuation\">(</span>newName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Todo: Verify that data has been updated and close the database</span></pre></div><h2 id=\"deleting-data\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deleting-data\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deleting data</h2><p>Like updating, data deletion using both SQLite libraries follows the same pattern as querying by using the <code>db.prepareQuery</code> or <code>Database.prepare</code> methods.</p>\n<h4 id=\"deleting-using-deno-sqlite\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deleting-using-deno-sqlite\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deleting using deno-sqlite</h4><div class=\"highlight\"><pre>  <span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n  <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> query <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token\"><span class=\"token function\">prepareQuery</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">[</span><span class=\"token\">number</span><span class=\"token punctuation\">,</span> <span class=\"token\">string</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> name<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">;</span> id<span class=\"token operator\">:</span> <span class=\"token\">number</span> <span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span>\n    <span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">DELETE from people where id=:id</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  query<span class=\"token punctuation\">.</span><span class=\"token function\">all</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  query<span class=\"token punctuation\">.</span><span class=\"token function\">finalize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Todo: Verify that data has been deleted and close the database</span></pre></div><h4 id=\"deleting-using-sqlite3\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deleting-using-sqlite3\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deleting using sqlite3</h4><div class=\"highlight\"><pre>  <span class=\"token comment\">// Todo: create a table and fill with data as above.</span>\n  <span class=\"token keyword\">const</span> id <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> stmt <span class=\"token operator\">=</span> db<span class=\"token punctuation\">.</span><span class=\"token function\">prepare</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">DELETE from people where id=?</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  stmt<span class=\"token punctuation\">.</span><span class=\"token function\">run</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Deleted record for id </span><span class=\"token\"><span class=\"token punctuation\">${</span>id<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  stmt<span class=\"token punctuation\">.</span><span class=\"token function\">finalize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Todo: Verify that data has been deleted and close the database</span></pre></div><p>When verifying that a record has been deleted you need to note that a query containing no results returns an empty array with <code>deno-sqlite</code> while the <code>sqlite3</code> lib returns undefined.</p>\n<h2 id=\"sqlite-backends\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#sqlite-backends\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>SQLite Backends</h2><p>SQLite was originally designed to be a lightweight database with data stored in a single file. As a consequence of that fact many web developers used it to do local development or to run integration and end-to-end tests.</p>\n<p>Modern web applications run in the cloud and all the major cloud platforms including <a href=\"https://aws.amazon.com/marketplace/pp/prodview-fci5iqpwrzxvo\" rel=\"noopener noreferrer\">Amazon Web Services</a>, <a href=\"https://azuremarketplace.microsoft.com/en-us/marketplace/apps/cloud-infrastructure-services.sqlite-ubuntu\" rel=\"noopener noreferrer\">Azure</a> and <a href=\"https://console.cloud.google.com/marketplace/product/cloud-infrastructure-services/sqlite-ubuntu\" rel=\"noopener noreferrer\">Google Cloud Platform</a> support SQLite on their platform. Besides the big three, other cloud providers with SQLite support include <a href=\"https://fly.io/docs/litefs/\" rel=\"noopener noreferrer\">fly.io</a> and <a href=\"https://www.digitalocean.com/community/tutorials/how-to-install-and-use-sqlite-on-ubuntu-20-04\" rel=\"noopener noreferrer\">Digital Ocean</a>.</p>\n<p>Distributed SQLite implementations are also available including <a href=\"https://fly.io/docs/litefs/\" rel=\"noopener noreferrer\">LiteFS</a> from fly.io <a href=\"https://rqlite.io/\" rel=\"noopener noreferrer\">RQLite</a>, <a href=\"https://dqlite.io/\" rel=\"noopener noreferrer\">DqLite by Canonical</a>, <a href=\"https://github.com/losfair/mvsqlite\" rel=\"noopener noreferrer\">mvSQLite</a> and <a href=\"https://dbhub.io/\" rel=\"noopener noreferrer\">DBHub</a>.</p>\n<p>An interesting <a href=\"https://kentcdodds.com/blog/i-migrated-from-a-postgres-cluster-to-distributed-sqlite-with-litefs\" rel=\"noopener noreferrer\">article on migrating from PostgreSQL to SQLite using LiteFS</a> shows what is involved with using a distributed SQLite implementation.</p>\n<h2 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h2><p>I have tried to provide an objective comparison in this post between the Deno-native SQLite libraries <code>deno-sqlite (sqlite)</code> and <code>sqlite3</code> and not play favorites. It is up to you to try each of them out and decide which one works for your use case.</p>\n<p>This post covers a subset of the <code>deno-sqlite</code> and <code>sqlite3</code> APIs, so it is a good idea to check the documentation for more details.</p>\n<p>Documentation for the <code>deno-sqlite</code> lib is found in the <a href=\"https://deno.land/x/sqlite@v3.7.0/mod.ts\" rel=\"noopener noreferrer\">third-party registry pages for <code>sqlite</code></a> where you need to drill-down though the hyperlinks for function and TypeScript interface documentation. This documentation is generated from the jsdoc source-code comments for each TS interface and JavaScript public function and class.</p>\n<p>Documentation for the <code>sqlite3</code> library is more centralized in the repo's <a href=\"https://github.com/denodrivers/sqlite3/blob/main/doc.md\" rel=\"noopener noreferrer\"><code>doc.md</code></a> file.</p>\n<p>Finally, make sure you check out the <a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/sqlite\" rel=\"noopener noreferrer\">companion Github Repository to this article</a> to see working examples of all the CRUD operations for both libraries discussed here.</p>\n<h2 id=\"notes\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#notes\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Notes</h2><h5 id=\"1-deno-native\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#1-deno-native\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>1. Deno-native</h5><p>Deno-native is used here to indicate that the module is a Deno third-party library compatible with Deno and not an npm module requiring the use of the <code>npm:</code> prefix in the import URL.</p>\n",
            "url": "https://deno-blog.com/Using_SQLite_with_Deno.2023-02-24",
            "title": "Using SQLite with Deno",
            "summary": "Using SQLite with Deno",
            "date_modified": "2023-02-24T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/Building_Fullstack_React_Apps_with_Ultra.2022-12-04",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2022-12-04\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2022-12-04\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2022-12-04</h4><h5 id=\"11-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#11-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>11 min read</em></h5><h1 id=\"building-fullstack-react-apps-with-ultra\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#building-fullstack-react-apps-with-ultra\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Building Fullstack React Apps with Ultra</h1><p><a href=\"https://ultrajs.dev\" rel=\"noopener noreferrer\"><strong>Ultra</strong></a> is a full stack framework for building Deno webapps\nusing <a href=\"https://reactjs.org\" rel=\"noopener noreferrer\">React</a>. <strong>Ultra</strong> recently released version 2.0 that is more customizable than v1.</p>\n<p><strong>Ultra</strong> works by streaming React-generated HTML markup from the server. Version 2 of the app is designed to run under React 18+ which supports React Suspense. Suspense allows asynchronous loading of components that need to do some time-intensive work on the server -- such as data fetching -- before they are rendered. The <code>React.Suspense</code> component provides a <code>fallback</code> prop to display a loading indicator component before the suspended child component is rendered.</p>\n<p>This article will demonstrate how to use <strong>Ultra</strong> to create and deploy a React app. Source code can be found <a href=\"https://github.com/cdoremus/ultra2-demo\" rel=\"noopener noreferrer\">in this Github repo</a> and the application is <a href=\"https://ultra2-demo.deno.dev\" rel=\"noopener noreferrer\">deployed on Deno Deploy here</a>.</p>\n<h2 id=\"creating-an-ultra-app\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#creating-an-ultra-app\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Creating an Ultra App</h2><p>The best way to create a <strong>Ultra</strong> project is to use the create CLI script\n(<code>create.ts</code>). You run it by invoking:</p>\n<div class=\"highlight\"><pre>deno run <span class=\"token operator\">-</span><span class=\"token\">A</span> <span class=\"token operator\">-</span>r https<span class=\"token operator\">:</span><span class=\"token operator\">/</span><span class=\"token operator\">/</span>deno<span class=\"token punctuation\">.</span>land<span class=\"token operator\">/</span>x<span class=\"token operator\">/</span>ultra<span class=\"token operator\">/</span>create<span class=\"token punctuation\">.</span>ts</pre></div><p>The script will then ask for the project name and whether you want to use TypeScript or JavaScript. The project name will become the folder name where the project exists under the folder where you ran the script, so it's best not to have any spaces in the project name.</p>\n<p>The script will then request what libraries you would like to use with your <strong>Ultra</strong> app. The options are for:</p>\n<ul>\n<li>Styling: Tailwind (twind), Stitches or no CSS library. Static CSS is still an option as the script will create a <code>style.css</code> file.</li>\n<li>Routing: React Router, Wouter or no routing are the options.</li>\n<li>HTML head management: To use React Helmet or nothing. If nothing is chosen, HTML is added to the return value of the <code>App</code> component inside <code>app.tsx</code> with HEAD and BODY elements.</li>\n<li>Data access: React Query or no data access library.</li>\n</ul>\n<p>Other React libraries still can be used in a an <strong>Ultra</strong> app, These libraries and other options are demonstrated in the <a href=\"https://github.com/exhibitionist-digital/ultra/tree/main/examples\" rel=\"noopener noreferrer\"><code>examples</code> folder of the <strong>Ultra</strong> repo</a>.</p>\n<h3 id=\"key-app-files-and-folders\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#key-app-files-and-folders\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Key app files and folders</h3><p>The key files and folders in an <strong>Ultra</strong> app include code to compile and run the app, and setup code for many libraries so it is important to know what is going on with them.</p>\n<p>They include:</p>\n<ul>\n<li><p><code>importMap.json</code>: The HTML-standard <a href=\"https://html.spec.whatwg.org/multipage/webappapis.html#import-maps\" rel=\"noopener noreferrer\">import map</a> that allows an alias to be used in place of a JavaScript/TypeScript url in an ECMAScript import statement.</p>\n</li>\n<li><p><code>deno.json</code>: The <a href=\"https://deno.land/manual@v1.28.3/getting_started/configuration_file\" rel=\"noopener noreferrer\">Deno config file</a> used for various things including command line scripts (like the scripts section of <code>package.json</code>) under the <code>\"tasks\"</code> property. Invoke a task using the <code>deno task &lt;task name&gt;</code> command. Make sure you reference the path to the app's import map as the value of the <code>\"importMap\"</code> property. Also, with the <code>\"lint\"</code> and <code>\"fmt\"</code> properties you'll want to exclude the <code>.ultra</code> folder.</p>\n</li>\n<li><p><code>server.ts</code>: This is the app's entry point as it is invoked on the Deno command line when the <code>\"dev\"</code> and <code>\"start\"</code> tasks are run. It works with the <a href=\"https://honojs.dev\" rel=\"noopener noreferrer\"><code>Hono</code></a> server library. React Router and other libraries need to have setup code put in this file. This can be tricky which is why I suggest using the <code>create.ts</code> app to add libraries.</p>\n</li>\n<li><p><code>client.ts</code>: This is the client's entry point that hydrates the app when it is rendered in the browser. The <code>ClientApp</code> function returns the home page with components and should be wrapped in context providers needed for the app to function.</p>\n</li>\n<li><p><code>build.ts</code>: Used to build the app and dependencies into a <code>.ultra</code> folder. Add any files you do not want to be deployed to the <code>builder.ignore</code> call in that file. Also put code into this file for any additional build compilation or transformations. For instance, an MDX compile step is done in the <code>with-mdx</code> example. Also note build options detailed in the <code>lib/build/types.ts</code> file in the <strong>Ultra</strong> repo.</p>\n</li>\n<li><p><code>src/app.tsx</code>: The React app's entry point. The <code>create.ts</code> script will generate an <code>App</code> component in this file that returns example content wrapped in an HTML tag that includes HEAD and BODY elements.</p>\n</li>\n<li><p><code>public folder</code>: This app holds the app's assets including images, CSS files and other static content.</p>\n</li>\n<li><p><code>.ultra folder</code>: Holds the result of an <strong>Ultra</strong> build that will get deployed to production.</p>\n</li>\n</ul>\n<h2 id=\"demo-app\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#demo-app\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Demo app</h2><p>I created a TypeScript app that used Tailwind, React Router and React Query. I employed the create script to scaffold out the app which adds context providers for React Router and React Query to <code>server.ts</code> and/or <code>client.ts</code>.</p>\n<p>My app uses the <a href=\"https://jsonplaceholder.typicode.com/\" rel=\"noopener noreferrer\">jsonplaceholder API</a> with React Query to display fake users and blog posts. You can see it in action <a href=\"https://ultra2-demo.deno.dev\" rel=\"noopener noreferrer\">here</a>. To run the app locally invoke <code>deno task dev</code> from the command line. Find the app's code in <a href=\"https://github.com/cdoremus/ultra2-demo\" rel=\"noopener noreferrer\">this repo</a>.</p>\n<h3 id=\"react-router\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#react-router\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>React Router</h3><p>The <a href=\"https://reactrouter.com/en/main\" rel=\"noopener noreferrer\"><code>React Router</code></a> (v6) context provider was setup in both <code>server.ts</code> and <code>client.ts</code>. Note that the <code>StaticRouter</code> is used for server-side rendering while the client file uses <code>BrowserRouter</code>.</p>\n<p>The routes for both server and client are configured in <code>app.tsx</code>. Here's what that looks like:</p>\n<div class=\"highlight\"><pre>  <span class=\"token operator\">&lt;</span>Routes<span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>Route path<span class=\"token operator\">=</span><span class=\"token string\">\"/\"</span> element<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>Layout <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>Route index element<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>HomePage <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>Route path<span class=\"token operator\">=</span><span class=\"token string\">\"about\"</span> element<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>AboutPage <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>Route path<span class=\"token operator\">=</span><span class=\"token string\">\"user_details/:userId\"</span> element<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>UserDetailsPage <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>Route path<span class=\"token operator\">=</span><span class=\"token string\">\"*\"</span> element<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>RouteNotFound <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>Route<span class=\"token operator\">&gt;</span>\n  <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>Routes<span class=\"token operator\">&gt;</span></pre></div><p>Each of the routes point to a page component that wraps the page's content. The <code>Layout</code> component defines a Layout Route which forms a shell around other components containing the app's header and footer. Routes defined inside the Layout Route hold the content (<code>HomePage</code>, <code>AboutPage</code>, <code>UserDetailsPage</code> and <code>RouteNotFound</code> page in this case).</p>\n<p>The <code>Layout</code> component uses an <code>Outlet</code> component to defined where the child components go as the <code>children</code> prop did previously. This is what that component's return value looks like (styling removed):</p>\n<div class=\"highlight\"><pre>  <span class=\"token operator\">&lt;</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>header<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>nav<span class=\"token operator\">&gt;</span>\n        <span class=\"token operator\">&lt;</span>NavLink to<span class=\"token operator\">=</span><span class=\"token string\">\"/\"</span><span class=\"token operator\">&gt;</span>Home<span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>NavLink<span class=\"token operator\">&gt;</span>\n        <span class=\"token operator\">&lt;</span>NavLink to<span class=\"token operator\">=</span><span class=\"token string\">\"/about\"</span><span class=\"token operator\">&gt;</span>About<span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>NavLink<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>nav<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>div<span class=\"token operator\">&gt;</span>\n        <span class=\"token operator\">&lt;</span>h1<span class=\"token operator\">&gt;</span>Ultra Demo App<span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>h1<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>header<span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>main<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>Outlet<span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span><span class=\"token comment\">/* Child components here */</span><span class=\"token punctuation\">}</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>main<span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>footer<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>div<span class=\"token operator\">&gt;</span>\n        <span class=\"token operator\">&lt;</span>a href<span class=\"token operator\">=</span><span class=\"token string\">\"https://ultrajs.dev\"</span><span class=\"token operator\">&gt;</span>\n          Built <span class=\"token keyword\">with</span> Ultra\n        <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>a<span class=\"token operator\">&gt;</span>💎\n      <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>footer<span class=\"token operator\">&gt;</span>\n  <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span></pre></div><p>See the <a href=\"https://reactrouter.com/en/main\" rel=\"noopener noreferrer\">React Router docs</a> for more details on version 6 of the library.</p>\n<h3 id=\"react-query\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#react-query\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>React Query</h3><p><a href=\"https://tanstack.com/query/v4/docs/adapters/react-query\" rel=\"noopener noreferrer\"><code>ReactQuery</code></a> is a data management API used in the app to make API calls for user data. It provides intelligent caching, prefetching and pagination features among others. Version 4 also supports React suspense for asynchronous data fetching.</p>\n<p>React Query setup in <strong>Ultra</strong> is somewhat complicated, so it is advised that you bring it in when running the <code>create.ts</code> project-creation script. You'll noticed that a <code>src/react-query</code> folder has been created. The <code>query-client.ts</code> file inside that folder initializes a <code>QueryClient</code> class containing a <code>suspense: true</code> option for React suspense support.</p>\n<p>The <code>useDehydrateReactQuery.tsx</code> file in the <code>react-query</code> folder uses the helper hook <code>useServerInsertedHTML</code> included in the <strong>Ultra</strong> distribution. The <code>useDehydrateReactQuery</code> function serializes the query client's fetched data on the server side storing it in a <code>window</code> property called <code>__REACT_QUERY_DEHYDRATED_STATE</code>. This is all done when <code>server.tsx</code> is invoked at app startup.</p>\n<p>Query data rehydration is done in <code>client.tsx</code> using the <code>Hydrate</code> component from React Query.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// client.ts</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Hydrate <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"@tanstack/react-query\"</span><span class=\"token punctuation\">;</span></pre></div><p>This component is added to the JSX returned from the <code>ClientApp()</code> function:</p>\n<div class=\"highlight\"><pre>  <span class=\"token comment\">// Other code is missing</span>\n  <span class=\"token operator\">&lt;</span>Hydrate state<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>__REACT_QUERY_DEHYDRATED_STATE<span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>BrowserRouter<span class=\"token operator\">&gt;</span>\n      <span class=\"token operator\">&lt;</span>App <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>BrowserRouter<span class=\"token operator\">&gt;</span>\n  <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>Hydrate<span class=\"token operator\">&gt;</span></pre></div><p>Recall that <code>__REACT_QUERY_DEHYDRATED_STATE</code> was populated in <code>server.tsx</code>.</p>\n<p><code>QueryClientProvider</code>, react query's context provider, is added to both the client (<code>client.ts</code>) and server (<code>server.ts</code>) file. It is imported from <code>@tanstack/react-query</code>.</p>\n<h3 id=\"tailwind\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#tailwind\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Tailwind</h3><p>The popular <a href=\"https://tailwindcss.com/\" rel=\"noopener noreferrer\"><code>Tailwind</code></a> CSS class collection is supported by the <a href=\"https://twind.dev\" rel=\"noopener noreferrer\">twind</a> library. <code>Twin</code> compiles Tailwind CSS classes into generic CSS on the fly so it is ideal for <strong>Ultra's</strong> streaming server.</p>\n<p>Tailwind classes are expressed using the <code>tw</code> function. Here is an example how it is used:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> tw <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./twind/twind.ts\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// other code here</span>\n  <span class=\"token operator\">&lt;</span>h2 className<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token function\">tw</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">text-3xl font-bold</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n    About <span class=\"token keyword\">this</span> Site\n  <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>h2<span class=\"token operator\">&gt;</span></pre></div><p>Check the <a href=\"https://v2.tailwindcss.com/docs\" rel=\"noopener noreferrer\">Tailwind docs</a> for details on the available <code>Tailwind</code> classes. Support for <code>Twind</code> 1.0 was recently added to <strong>Ultra</strong> which is compatible with <code>Tailwind</code> version 3.</p>\n<h2 id=\"using-suspense\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-suspense\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using Suspense</h2><p><strong>Ultra</strong> version 2 works with React v18. A big feature of this new React version is suspense. React suspense allows a component to be asynchronously rendered. This means that part of the UI can be displayed while suspended components are still being rendered.</p>\n<p>Setting up suspense involves wrapping a component with the <code>Suspense</code> component. This is what that looks like:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// Home.tsx</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> Suspense <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"react\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// other code here...</span>\n  <span class=\"token operator\">&lt;</span>Suspense fallback<span class=\"token operator\">=</span>\n    <span class=\"token punctuation\">{</span><span class=\"token operator\">&lt;</span>div<span class=\"token operator\">&gt;</span>Page <span class=\"token keyword\">is</span> Loading<span class=\"token operator\">...</span><span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span><span class=\"token punctuation\">}</span>\n  <span class=\"token operator\">&gt;</span>\n    <span class=\"token operator\">&lt;</span>UserList <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n  <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>Suspense<span class=\"token operator\">&gt;</span></pre></div><p>In this case the <code>UserList</code> component is being suspended. Note the <code>fallback</code> prop that is used to define a component that will be displayed while the suspended component is still being rendered. Once that is completed, the suspended component will replace the fallback component.</p>\n<p>When a <code>Suspense</code> component is used in an application page, the page needs to be lazy loaded with a dynamic import:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// app.tsx</span>\n<span class=\"token keyword\">const</span> HomePage <span class=\"token operator\">=</span> <span class=\"token function\">lazy</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token keyword\">import</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"./pages/Home.tsx\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h2 id=\"hono\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#hono\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Hono</h2><p>As noted above <strong>Ultra</strong> uses the <a href=\"https://honojs.dev\" rel=\"noopener noreferrer\">Hono</a> Deno server under the covers. Hono's Deno server is based on the http server in the Deno standard library. But Hono adds value that can be used in an <strong>Ultra app</strong>. A big one is middleware.</p>\n<p>The <code>createServer</code> call in <code>server.tsx</code> returns a Hono server object in a variable called <code>server</code>. Middleware is added with a call to <code>server.get</code>. The first argument is a string representing an http path. The second argument is a handler function with a <a href=\"https://honojs.dev/docs/api/context/\" rel=\"noopener noreferrer\"><code>Context</code></a> first argument and <code>next</code> function as the second argument.</p>\n<p>The handler function can either return a <code>Response</code> or <code>Promise&lt;Response | undefined | void&gt;</code> or invoke the <code>next</code> function using an <code>await</code> since <code>next</code> returns a <code>Promise</code>. The <code>next</code> function passes the request flow onto the next <code>server.get</code> located in <code>server.tsx</code> (see the definition of <code>Handler</code> in <code>src/types.ts</code> in the Hono repo and the <a href=\"https://honojs.dev/docs/api/middleware/\" rel=\"noopener noreferrer\">Middleware docs</a>).</p>\n<p>Here is an example of middleware that adds a header to the response called \"Server\" with a value of \"Ultra Hono\":</p>\n<div class=\"highlight\"><pre>server<span class=\"token punctuation\">.</span><span class=\"token function\">use</span><span class=\"token punctuation\">(</span><span class=\"token string\">'*'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span>c<span class=\"token punctuation\">,</span> next<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  c<span class=\"token punctuation\">.</span>res<span class=\"token punctuation\">.</span>headers<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Server\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"Ultra Hono\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">await</span> <span class=\"token function\">next</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>Hono provides a bunch of <a href=\"https://honojs.dev/docs/builtin-middleware/\" rel=\"noopener noreferrer\">built-in middleware functions</a> including ones for authentication, CORS support and serving static files.</p>\n<p>Hono can also be used for server-side routing. If authentication middleware is used, then server-side routing is required.</p>\n<p>You can create an API route using Hono too. See the <a href=\"https://github.com/exhibitionist-digital/ultra/tree/main/examples/with-api-routes\" rel=\"noopener noreferrer\">with-api-routes <strong>Ultra</strong> example</a> to learn how this is done in addition to the <a href=\"https://honojs.dev/docs/api/routing/\" rel=\"noopener noreferrer\">Hono routing docs</a>.</p>\n<p>Note that the Hono API supports Node and Cloudfare Workers in addition to Deno. See the <a href=\"https://honojs.dev/docs/getting-started/deno/\" rel=\"noopener noreferrer\">Hono Deno docs</a> for more details on the Hono Deno server.</p>\n<h2 id=\"other-libraries\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#other-libraries\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Other Libraries</h2><p>There are over 20 examples in the <strong>Ultra</strong> repo's <a href=\"https://github.com/exhibitionist-digital/ultra/tree/main/examples\" rel=\"noopener noreferrer\">examples folder</a>. Most of them show how to use React libraries with <strong>Ultra</strong>. They include (besides libs detailed above):</p>\n<ul>\n<li><a href=\"https://mui.com/\" rel=\"noopener noreferrer\">Material UI</a>: A collection of React components.</li>\n<li><a href=\"https://trpc.io/\" rel=\"noopener noreferrer\">tRCP</a>: a library for creating type-safe APIs.</li>\n<li><a href=\"https://mdxjs.com\" rel=\"noopener noreferrer\">mdx</a>: converts markdown into JSX content.</li>\n<li><a href=\"https://emotion.sh/\" rel=\"noopener noreferrer\">emotion</a>: a CSS-in-JS library.</li>\n<li><a href=\"https://github.com/nfl/react-helmet#readme\" rel=\"noopener noreferrer\">react-helmet</a>: a component to add an HTML Head element to a JSX page.</li>\n<li><a href=\"https://preactjs.com/\" rel=\"noopener noreferrer\">with-preact</a>: Preact is a lightweight React port. Note that not all React libraries work with Preact.</li>\n<li><a href=\"https://github.com/exhibitionist-digital/ultra/tree/main/examples/bogus-marketing-or-blog\" rel=\"noopener noreferrer\">static HTML</a>: See the <code>examples/bogus-marketing-or-blog</code> folder. Note the <code>generateStaticHTML</code> and <code>disableHydration</code> properties added to the <code>server.render</code> function call in <code>server.tsx</code></li>\n<li><a href=\"https://www.patterns.dev/posts/islands-architecture/\" rel=\"noopener noreferrer\">island architecture</a> - this structures the app where islands of JavaScript-related reactivity are surrounded by static HTML content. It is how Deno Fresh works.</li>\n</ul>\n<p>When adapting one of these examples to your application, pay particular attention to changes in <code>server.tsx</code>, <code>client.tsx</code> and sometimes <code>build.ts</code> that allows the example to work with <strong>Ultra</strong>.</p>\n<h2 id=\"deployment\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#deployment\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Deployment</h2><p>There are two main options for deployment an <strong>Ultra</strong> app, using Docker to deploy to cloud hosts that support it like <a href=\"https://fly.io\" rel=\"noopener noreferrer\">fly.io</a> and <a href=\"https://deno.dev\" rel=\"noopener noreferrer\">Deno Deploy</a>. Instructions are found in the <a href=\"https://ultrajs.dev/docs#deploying\" rel=\"noopener noreferrer\">Ultra deployment docs</a>.</p>\n<p>When using Deno Deploy, you need to set <code>inlineServerDynamicImports: true</code> as a <code>createBuilder</code> option in <code>build.ts</code> since Deploy does not support dynamic imports. Also note the <a href=\"https://github.com/cdoremus/ultra2-demo/blob/main/.github/workflows/deploy.yml\" rel=\"noopener noreferrer\">Github Action</a> needed to use Deno Deploy with <strong>Ultra</strong>.</p>\n<p>Production deployment requires that all the images and other assets are wrapped in a <code>useAsset</code> hook from <strong>Ultra</strong> like this:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> useAsset <span class=\"token keyword\">from</span> <span class=\"token string\">\"ultra/hooks/use-asset.js\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token comment\">// other code here</span>\n  <span class=\"token operator\">&lt;</span>link rel<span class=\"token operator\">=</span><span class=\"token string\">\"shortcut icon\"</span> href<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token function\">useAsset</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/favicon.ico\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span></pre></div><p>The <code>useAsset</code> hook is used to version the asset during a production build.</p>\n<p>To build and dry-run the app before production deployment, invoke the <code>build</code> task locally and then run the <code>start</code> task inside the <code>.ultra</code> folder.</p>\n<h2 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h2><p><strong>Ultra</strong> is the third most popular Deno web framework next to Fresh and Aleph which are both supported by the Deno team. It is the only one of the three that focusses on React and does a good job at supporting React libraries and modern practices.</p>\n<p><strong>Ultra</strong> evolved dramatically between version 1 and 2 and its development continues to accelerate. When this post was published <strong>Ultra's</strong> current version was v2.1.4, so be aware that there might be some changes at a future date.</p>\n<p>At any rate, <strong>Ultra</strong> is a good option for creating React apps because it eliminates the build step with it's headache-inducing configurations therefore allowing you to focus on application development.</p>\n<p>Finally, make sure you check out my <a href=\"https://github.com/cdoremus/ultra2-demo\" rel=\"noopener noreferrer\"><strong>Ultra</strong> demo app repo</a> and the <a href=\"https://github.com/exhibitionist-digital/ultra/tree/main/examples\" rel=\"noopener noreferrer\">examples folder in the <strong>Ultra</strong> repo</a> for more ideas on how to use <strong>Ultra</strong> to create a Deno/React app.</p>\n",
            "url": "https://deno-blog.com/Building_Fullstack_React_Apps_with_Ultra.2022-12-04",
            "title": "Building Fullstack React Apps with Ultra",
            "summary": "Building Fullstack React Apps with Ultra",
            "date_modified": "2022-12-04T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/Using_Preact_Signals_with_Fresh.2022-11-01",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2022-11-01\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2022-11-01\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2022-11-01</h4><h5 id=\"9-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#9-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>9 min read</em></h5><h1 id=\"using-preact-signals-with-fresh\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-preact-signals-with-fresh\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using Preact Signals with Fresh</h1><p><a href=\"https://fresh.deno.dev\" rel=\"noopener noreferrer\">Fresh</a> is the Deno full-stack framework created by <a href=\"https://github.com/lucacasonato\" rel=\"noopener noreferrer\">Luca Casonato</a> of the Deno core team and now <a href=\"https://github.com/denoland/fresh\" rel=\"noopener noreferrer\">hosted in the Deno github repository</a>.This blog post assume you have some familiarity with Fresh. If not, see the <a href=\"https://fresh.deno.dev/docs/introduction\" rel=\"noopener noreferrer\">Fresh documentation</a> to get up to speed.</p>\n<p>Fresh uses <a href=\"https://preactjs.com\" rel=\"noopener noreferrer\">Preact</a> for the UI. Recently the Preact team has released a new library called <a href=\"https://preactjs.com/blog/introducing-signals/\" rel=\"noopener noreferrer\">Signals</a> for reactive state management. Preact Signals are used for both global and local state management.</p>\n<p>This post will demonstrate how to use Signals with Fresh. I created a basic Fresh Todo app to illustrate the use of Preact Signals. The source code can be found <a href=\"https://github.com/cdoremus/fresh-todo-signals\" rel=\"noopener noreferrer\">in this Github repo</a>.</p>\n<h2 id=\"global-state-management-with-preact-signals\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#global-state-management-with-preact-signals\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Global State Management with Preact Signals</h2><h3 id=\"setup\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#setup\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Setup</h3><p>As of Fresh version 1.1, when you <a href=\"https://fresh.deno.dev/docs/getting-started/create-a-project\" rel=\"noopener noreferrer\">create a new fresh project</a> the signals libraries are automatically added to the import map file.\nIf you are updating a Fresh project to use Signals, add the following lines to the <code>imports</code> section in your import map:</p>\n<pre><code>{\n  \"imports\": {\n    # other imports here\n    \"@preact/signals\": \"https://esm.sh/*@preact/signals@1.0.3\",\n    \"@preact/signals-core\": \"https://esm.sh/*@preact/signals-core@1.0.1\"\n  }\n}</code></pre><p>All the Signals functions we will be using are located in the <code>@preact/signals</code> module.</p>\n<h2 id=\"creating-global-state\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#creating-global-state\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Creating global state</h2><p>The Todo app encapsulates global state management inside a function called <code>createAppState</code> (in <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/state.ts\" rel=\"noopener noreferrer\"><code>state.ts</code></a>). The <code>signal</code> function is used to add a field to global state:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">function</span> <span class=\"token function\">createAppState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> AppStateType <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// create signals for todo array and current todo</span>\n  <span class=\"token keyword\">const</span> todos <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">signal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">string</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> currentTodo <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">signal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">string</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token string\">\"\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// other stuff here...</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>todos<span class=\"token punctuation\">,</span> currentTodo<span class=\"token punctuation\">,</span> <span class=\"token comment\">/* Other function properties here...*/</span><span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">// function return value is the default export</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token function\">createAppState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this case, the current todo (<code>currentTodo</code>) and a todos array (<code>todos</code>) are held in an object returned by <code>createAppState</code>. Each call to the <code>signal</code> function takes an object or primitive argument that represents the initial value of that signal.</p>\n<p>Functions that update the signals are also contained in <code>createAppState</code> (<code>addTodo</code> and <code>removeTodo</code>) and made available to other modules in the return value of that function (also note the TypeScript type <code>AppStateType</code> defined in <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/state.ts\" rel=\"noopener noreferrer\">state.ts</a>).</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">function</span> <span class=\"token function\">createAppState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> AppStateType <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// ... todos, current todos signals created here  as is todoCount ...</span>\n\n  <span class=\"token keyword\">const</span> addTodo<span class=\"token operator\">:</span> AddTodoFunction <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>e<span class=\"token operator\">:</span> Event<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    e<span class=\"token punctuation\">.</span><span class=\"token function\">preventDefault</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    todos<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token operator\">...</span>todos<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">,</span> currentTodo<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    currentTodo<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> removeTodo<span class=\"token operator\">:</span> RemoveTodoFunction <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>index<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token keyword\">void</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    todos<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> todos<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>_todo<span class=\"token operator\">:</span> <span class=\"token\">unknown</span><span class=\"token punctuation\">,</span> i<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span>\n      i <span class=\"token operator\">!==</span> index\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span> todos<span class=\"token punctuation\">,</span> currentTodo<span class=\"token punctuation\">,</span> addTodo<span class=\"token punctuation\">,</span> removeTodo<span class=\"token punctuation\">,</span> todoCount <span class=\"token comment\">/* todoCount explained below */</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token function\">createAppState</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>We will be using the <code>createAppState</code> function to pass signals and functions to the app's components. Ths done through the Preact\nContext (similar to React Context). Application state -- including the signals previously created -- is added to the context in the root component <code>App</code> (see <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/islands/App.tsx\" rel=\"noopener noreferrer\"><code>App.tsx</code></a>) using a context provider:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> createContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"preact\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> state<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> <span class=\"token keyword\">type</span> <span class=\"token class-name\">AppStateType</span> <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"../state.ts\"</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// create the AppState context</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">const</span> AppState <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">createContext</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span>AppStateType<span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span> <span class=\"token keyword\">as</span> AppStateType<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">App</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n      <span class=\"token operator\">&lt;</span>AppState<span class=\"token punctuation\">.</span>Provider value<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>state<span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>\n      <span class=\"token comment\">// Components that use the state here ...</span>\n      <span class=\"token operator\">&lt;</span>AppState<span class=\"token punctuation\">.</span>Provider<span class=\"token operator\">&gt;</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The <code>state</code> field in the <code>App</code> module is the return value of the <code>createAppState</code> function (see <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/state.ts\" rel=\"noopener noreferrer\"><code>state.ts</code></a>). <code>AppState.Provider</code> is a Preact context provider which exposes <code>state</code> to the rest of the app enclosed within its component tree.</p>\n<p>The <code>state</code> held in the context can be accessed by any of the app's components with the <code>useContext</code> function. Here it is used by the <code>TodoList</code> component to access the list of todos:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> useContext <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"preact/hooks\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> AppState <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"./App.tsx\"</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Todo <span class=\"token keyword\">from</span> <span class=\"token string\">\"./Todo.tsx\"</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">TodoList</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Destructure state to expose the todos signal</span>\n  <span class=\"token keyword\">const</span> <span class=\"token punctuation\">{</span> todos <span class=\"token punctuation\">}</span> <span class=\"token operator\">=</span> <span class=\"token function\">useContext</span><span class=\"token punctuation\">(</span>AppState<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token operator\">&lt;</span>div className<span class=\"token operator\">=</span><span class=\"token string\">\"todos\"</span><span class=\"token operator\">&gt;</span>\n    <span class=\"token comment\">// use the value property to obtain the todos array</span>\n      <span class=\"token punctuation\">{</span>todos<span class=\"token punctuation\">.</span>value<span class=\"token operator\">?.</span><span class=\"token function\">map</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span>item<span class=\"token operator\">:</span> <span class=\"token\">string</span><span class=\"token punctuation\">,</span> i<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span>\n          <span class=\"token operator\">&lt;</span>Todo text<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>item<span class=\"token punctuation\">}</span> index<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>i<span class=\"token punctuation\">}</span> <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span>\n    <span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>div<span class=\"token operator\">&gt;</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><h3 id=\"a-signals-value\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#a-signals-value\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>A Signal's value</h3><p>The object returned from a call to <code>signal</code> always contains a <code>value</code> field which is the current value of the signal. In the Todo app there are signals for all todos (<code>todos</code>, an array of strings) and the current todo (<code>currentTodo</code>, a string). The value of <code>todos</code> is obtained from this expression: <code>todos.value</code>.</p>\n<p>The <code>value</code> property can also be used to set the value of a signal. There is no built-in setter like there is in the <code>useState</code> hook. Signal value mutation is done in the <code>addTodo</code> and <code>removeTodo</code> functions. The same thing can be done outside the app state object returned from <code>createAppState</code>. This is what is done in the input's <code>onChange</code> handler in <code>AddTodo.tsx</code>:</p>\n<div class=\"highlight\"><pre>  <span class=\"token operator\">&lt;</span>input\n    <span class=\"token punctuation\">{</span> <span class=\"token comment\">/* other attrs missing here... */</span><span class=\"token punctuation\">}</span>\n    value<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span>currentTodo<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span>\n    onChange<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span>\n      currentTodo<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">.</span>target <span class=\"token keyword\">as</span> HTMLInputElement<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span>\n  <span class=\"token operator\">/</span><span class=\"token operator\">&gt;</span></pre></div><p>Note that the type coercion of the event target is needed because of a <a href=\"https://github.com/preactjs/preact/issues/1930\" rel=\"noopener noreferrer\">bug</a> in the current Preact TypeScript typings.</p>\n<h3 id=\"using-the-computed-function\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-the-computed-function\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using the computed function</h3><p>The function <code>computed</code> is included in the Preact Signals module. Signals that are used within <code>computed</code> are subscribed to the signal and notified when the signal's value changes. That allows <code>computed</code> to derive values based on the value of one or more signals. When <code>computed</code> is notified of a signal value change, it automatically re-runs the callback function sent in as an argument. An example is found in the <code>createAppState</code> function in <code>state.ts</code>:</p>\n<div class=\"highlight\"><pre>  <span class=\"token keyword\">const</span> todoCount <span class=\"token operator\">=</span> <span class=\"token function\">computed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> todos<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>When <code>todos.value</code> changes, the callback function gets re-run and <code>todoCount</code> gets updated with the new count.</p>\n<h2 id=\"local-state-with-signals\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#local-state-with-signals\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Local state with Signals</h2><p>The use of local signals is illustrated with the <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/islands/Counter.tsx\" rel=\"noopener noreferrer\">Counter.tsx</a> component that I've piggy-backed on to the Todo app for illustrative purposes.</p>\n<p>The <code>Counter</code> component counts button clicks and stores the counts in local storage so the current count can be recovered when the page is reloaded. Preact Signals is used to hold local state in this component.</p>\n<p>Using signals for local state requires use of the <code>useSignal</code> hook. There is also a <code>useComputed</code> function that can be used for local computed values.</p>\n<p>This is how the <code>Counter</code> component holds component state:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">Counter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> <span class=\"token\">COUNT_KEY</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"COUNT\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> count <span class=\"token operator\">=</span> <span class=\"token\"><span class=\"token function\">useSignal</span><span class=\"token class-name\"><span class=\"token operator\">&lt;</span><span class=\"token\">number</span><span class=\"token operator\">&gt;</span></span></span><span class=\"token punctuation\">(</span><span class=\"token function\">parseInt</span><span class=\"token punctuation\">(</span>localStorage<span class=\"token punctuation\">.</span><span class=\"token function\">getItem</span><span class=\"token punctuation\">(</span><span class=\"token\">COUNT_KEY</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">??</span> <span class=\"token string\">\"0\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// other code here...</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The count is stored in local storage, so when the count signal is instantiated using <code>useSignal</code>, it pulls the count from local storage if it exists. Note that <code>useSignal</code> is for local state, while <code>signal</code> is used for global state. Also note that <code>useSignal</code> does not return a setter function like <code>useState</code>. The state value can be obtained from the signal (<code>count.value</code> in this case) and that value can be mutated directly as occurs in this button's <code>onClick</code> handler (in <code>Counter.tsx</code>):</p>\n<div class=\"highlight\"><pre>  <span class=\"token operator\">&lt;</span>button onClick<span class=\"token operator\">=</span><span class=\"token punctuation\">{</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    count<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">;</span>\n    localStorage<span class=\"token punctuation\">.</span><span class=\"token function\">setItem</span><span class=\"token punctuation\">(</span><span class=\"token\">COUNT_KEY</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"0\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span>Reset Count<span class=\"token operator\">&lt;</span><span class=\"token operator\">/</span>button<span class=\"token operator\">&gt;</span></pre></div><p>The <code>useComputed</code> function is used for computing new values based on one or more signal's value. It is used for local state calculations, while <code>computed</code> is used for global state calculations. Both variations are subscribed to any signal that is contained within its function argument, so when the signal is updated, both  <code>useComputed</code> and <code>computed</code> function arguments are automatically invoked. In the case of the <code>Counter</code> component, <code>useComputed</code> is used to calculate the counter value squared. Any time that a signal changes, the <code>useComputed</code> function is re-run if it uses that signal (<code>count</code> in <code>Counter.tsx</code>).</p>\n<div class=\"highlight\"><pre>  <span class=\"token keyword\">const</span> square <span class=\"token operator\">=</span> <span class=\"token function\">useComputed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> count<span class=\"token punctuation\">.</span>value <span class=\"token operator\">*</span> count<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"the-effect-function\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#the-effect-function\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>The effect function</h3><p>The <code>effect</code> function in the <code>signals</code> module is used to handle side effects like <code>useEffect</code>. However, <code>effect</code> does not have a dependency array like <code>useEffect</code>. Instead the effect is subscribed to signals contained within the callback function, so when a signal's value changes, the <code>effect</code> function is notified of that change and the callback is invoked. In <code>Counter</code>, the effect is used to update the count in local storage.</p>\n<div class=\"highlight\"><pre>  <span class=\"token keyword\">const</span> dispose <span class=\"token operator\">=</span> <span class=\"token function\">effect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n      localStorage<span class=\"token punctuation\">.</span><span class=\"token function\">setItem</span><span class=\"token punctuation\">(</span><span class=\"token\">COUNT_KEY</span><span class=\"token punctuation\">,</span> count<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">Double: </span><span class=\"token\"><span class=\"token punctuation\">${</span>square<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token function\">useEffect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token comment\">// free-up effect's memory</span>\n      <span class=\"token function\">dispose</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>effect</code> function returns a function (called <code>dispose</code> in this case) that can be used to invalidate the effect and free up memory that it uses. In this case we call <code>dispose</code> in the function returned by <code>useEffect</code> which gets called when the component is unmounted.</p>\n<p>An <code>effect</code> can be nested inside another <code>effect</code>. Then you can make sure the signal only runs once like this:</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">// From Ryan Carniato stream: https://www.youtube.com/watch?v=QRtrS_SvR4w&amp;t=10909s</span>\n<span class=\"token keyword\">let</span> dispose<span class=\"token punctuation\">;</span>\n<span class=\"token function\">effect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// clean up signal after it runs once</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>dispose<span class=\"token punctuation\">)</span> <span class=\"token function\">dispose</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// outer side effect here</span>\n  dispose <span class=\"token operator\">=</span> <span class=\"token function\">effect</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// inner side effect here</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this code, the first time the outer <code>effect</code> callback is invoked, the <code>dispose</code> function is not run because it has not been assigned a value, but after the inner <code>effect</code> callback is invoked, then the outer <code>effect</code> disposes it.</p>\n<p>The <code>effect</code> function can be used to work with both global and local state.</p>\n<h2 id=\"other-functions-in-the-signals-module\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#other-functions-in-the-signals-module\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Other functions in the Signals module</h2><h4 id=\"the-batch-function\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#the-batch-function\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>The batch function</h4><p>The <code>batch</code> function is used to update multiple signals at a time for performance optimization. For instance, the todo array and current todo could be updated together in this manner:</p>\n<div class=\"highlight\"><pre><span class=\"token function\">batch</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  currentTodo<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> <span class=\"token string\">\"Another thing I have to do\"</span><span class=\"token punctuation\">;</span>\n  todos<span class=\"token punctuation\">.</span>value <span class=\"token operator\">=</span> todos<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>currentTodo<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><h4 id=\"the-peek-function\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#the-peek-function\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>The peek function</h4><p>The <code>peek</code> function is attached to a signal's value. It is used to get the value of a signal without subscribing to the signal. Once <code>peek</code> is called on a signal, any updates to the signal's value are not passed along to the return value. For example:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">const</span> countNow <span class=\"token operator\">=</span> count<span class=\"token punctuation\">.</span>value<span class=\"token punctuation\">.</span><span class=\"token function\">peek</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>In this case, <code>countNow</code> will not be updated if <code>count.value</code> changes after the call to <code>count.value.peak</code>.</p>\n<h2 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h2><p>This post shows how to use the Preact signals module with Deno Fresh. Snippets from the post's <a href=\"https://github.com/cdoremus/fresh-todo-signals\" rel=\"noopener noreferrer\">source code Github repo</a> have been used here, so make sure you check it out to get a complete picture of how everything fits together. Also see the <a href=\"https://github.com/cdoremus/fresh-todo-signals/blob/main/README.md\" rel=\"noopener noreferrer\">README.md</a> file for details on how to run the app locally.\nFinally, you should review the <a href=\"https://preactjs.com/guide/v10/signals\" rel=\"noopener noreferrer\">Preact Signals documentation</a> for more details on the Signals API and its use.</p>\n",
            "url": "https://deno-blog.com/Using_Preact_Signals_with_Fresh.2022-11-01",
            "title": "Using Preact Signals with Fresh",
            "summary": "Using Preact Signals with Fresh",
            "date_modified": "2022-11-01T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/Processing_CSV_files_with_Deno.2022-09-20",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2022-09-20\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2022-09-20\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2022-09-20</h4><h5 id=\"7-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#7-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>7 min read</em></h5><h1 id=\"processing-csv-files-with-deno\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#processing-csv-files-with-deno\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Processing CSV files with Deno</h1><p>The CSV (Comma-Separated-Values) format is one of the most common ways to do\ndata interchange between systems. The Deno standard library contains a\n<code>encoding/csv.ts</code> module to facilitate reading and writing CSV files. The\n<code>stringify</code> function in that module converts a JavaScript (or JSON) object into\na CSV line while the <code>parse</code> function converts a CSV-formatted line back into an\nobject. This post explores transforming API data into CSV and back to JS\nobjects. Source code for this blog post can be found at\n<a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/processing_csv\" rel=\"noopener noreferrer\">https://github.com/cdoremus/deno-blog-code/tree/main/processing_csv</a>.</p>\n<h2 id=\"writing-and-reading-csv-text\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#writing-and-reading-csv-text\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Writing and reading CSV text</h2><p>A small amount of data is easily processed in a single write and read to and\nfrom a text file. In this case we use the <code>Deno.writeTextFile</code> and\n<code>Deno.readTextFile</code> functions to put or get CSV data in a single call.</p>\n<h3 id=\"fetching-the-data\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#fetching-the-data\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Fetching the data</h3><p>We will be processing JSON data from an API called\n<a href=\"https://jsonplaceholder.typicode.com/\" rel=\"noopener noreferrer\">JSONPlaceholder</a> which provides fake\ndata for many common resources like users, blog posts and todos. The JavaScript\nbuilt-in <code>fetch</code> function will be used to call the API. Here's what a call to\nget a list of users looks like:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">fetchData</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span>User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> resp <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"https://jsonplaceholder.typicode.com/users\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> resp<span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The TypeScript-defined <code>User</code> type returned in a JS promise contains id, name\nand email keys.</p>\n<h3 id=\"writing-csv-data-to-a-file\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#writing-csv-data-to-a-file\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Writing CSV data to a file</h3><p>Once the API data has been fetched, the <code>stringify</code> function from the\n<code>encoding/cvs.ts</code> module is used to format the data into CSV before it is\nwritten to a text file:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">writeToFile</span><span class=\"token punctuation\">(</span>users<span class=\"token operator\">:</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span><span class=\"token keyword\">void</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> stringified <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>users<span class=\"token punctuation\">,</span> <span class=\"token\">USER_COLS</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">writeTextFile</span><span class=\"token punctuation\">(</span><span class=\"token\">USER_FILE</span><span class=\"token punctuation\">,</span> stringified<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The <code>stringify</code> function takes an array of data to be persisted as CSV as the\nfirst argument and a string array of columns. Make sure that data and the column\narray are in the same order. The <code>users</code> array is filled with user objects\nhaving id, name and email keys corresponding to the JSON returned from the API\ncall. The <code>USER_COLS</code> object is a string array containing the names of the\nobject keys.</p>\n<p>The <code>encoding/csv.ts</code> module's <code>stringify</code> function has an optional third\nargument that is an object with <code>headers</code> and <code>separator</code> keys. The <code>headers</code>\nkey (true by default) indicates whether a header containing the data's keys (id,\nname, email in this case) should be written to the file as the first line. The\n<code>separator</code> key indicates the delimiter used to separate items (a comma by\ndefault, but any character can be used as a separator).</p>\n<p>Finally, the delimited items are written to a file using the\n<code>Deno.writeTextFile</code> function.</p>\n<p>See\n<a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/processing_csv/populate_data.ts\" rel=\"noopener noreferrer\">populate_data.ts</a>\nin the blog source code repo for more details.</p>\n<h3 id=\"reading-csv-from-a-file\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#reading-csv-from-a-file\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Reading CSV from a file</h3><p>Reading CSV from a small file is done using <code>Deno.readTextFile</code>. In addition,\nthe <code>encoding/csv.ts</code> module contains a <code>parse</code> function that facilitates the\ntransformation of CSV data into an array of objects.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">readCsvFile</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span>User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> file <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">readTextFile</span><span class=\"token punctuation\">(</span><span class=\"token\">USERS_FILE</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> skipFirstRow<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span> columns<span class=\"token operator\">:</span> <span class=\"token\">USER_COLS</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">as</span> User<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The second argument of <code>parse</code> contains an options argument which is an object\nwith <code>skipFirstRow</code> and <code>columns</code> keys. The <code>skipFirstRow</code> key indicates if you\nshould skip the first row which contains the column names (<code>false</code> by default).\nThis (<code>skipfirstRow</code>) needs to be set to <code>true</code> if you set <code>headers</code> to <code>true</code>\nin the <code>stringify</code> call. The <code>columns</code> key is an ordered array of column\nheaders. You need to make sure that the <code>columns</code> order corresponds to the order\nof data in each CSV row.</p>\n<p>The <code>columns</code> values will be used as the keys for the objects being created from\nthe CSV data. Otherwise, you will just get an array of string arrays containing\nthe CSV data, so if you want the original object recreated, then <code>columns</code> needs\nto be used in your <code>parse</code> call.</p>\n<p>See\n<a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/processing_csv/read_data.ts\" rel=\"noopener noreferrer\">read_data.ts</a>\nin the blog source code repo for more details.</p>\n<h2 id=\"working-with-large-data-sets\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#working-with-large-data-sets\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Working with large data sets</h2><h3 id=\"writing-the-data-set\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#writing-the-data-set\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Writing the data set</h3><p>When a large amount of data is needed to be processed, it cannot be done in a\nsingle operation due to memory constraints. In that case, the <code>Deno.open</code>\nfunction can be used. That function takes two arguments. The first is the\nabsolute or relative file path while the second is an object with <code>read</code>,\n<code>write</code>, <code>append</code>, <code>create</code> and <code>createNew</code> keys. The other <code>Deno.open</code> keys,\n<code>truncate</code> and <code>mode</code>, are not relevant here.</p>\n<p>We will be using the JSONPlaceholder post API here:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">fetchData</span><span class=\"token punctuation\">(</span>page_number<span class=\"token operator\">:</span> <span class=\"token\">number</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span>Post<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> resp <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetch</span><span class=\"token punctuation\">(</span>\n    <span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">https://jsonplaceholder.typicode.com/posts?_limit=10&amp;_page=</span><span class=\"token\"><span class=\"token punctuation\">${</span>page_number<span class=\"token punctuation\">}</span></span><span class=\"token string\">`</span></span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">await</span> resp<span class=\"token punctuation\">.</span><span class=\"token function\">json</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>The JSONPlaceholder posts API only contains 100 records. We will be writing 10\npages (chunks) of 10 records each. I created a TypeScript type <code>Post</code> that\ncontains the keys from the post API (userId, id, title and body).</p>\n<p>Each of the post data chunks are written to the CSV file using the <code>stringify</code>\nfunction after a <code>Deno.open</code> call. Files opened in this manner need to be\nwritten as a byte array (<code>Uint8Array</code>).</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">writeToFileChunk</span><span class=\"token punctuation\">(</span>posts<span class=\"token operator\">:</span> Post<span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> file <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">open</span><span class=\"token punctuation\">(</span><span class=\"token\">POSTS_FILE</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    append<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> stringified <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span>posts<span class=\"token punctuation\">,</span> <span class=\"token\">POST_COLS</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> headers<span class=\"token operator\">:</span> <span class=\"token boolean\">false</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> byteArray <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Uint8Array</span><span class=\"token punctuation\">(</span>stringified<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> encoder <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">TextEncoder</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    encoder<span class=\"token punctuation\">.</span><span class=\"token function\">encodeInto</span><span class=\"token punctuation\">(</span>stringified<span class=\"token punctuation\">,</span> byteArray<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    file<span class=\"token punctuation\">.</span><span class=\"token function\">write</span><span class=\"token punctuation\">(</span>byteArray<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">finally</span> <span class=\"token punctuation\">{</span>\n    file<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></pre></div><p>A file reference obtained from calling <code>Deno.open</code> needs to be manually closed\nwhen finished. The best way to do this is in a <code>finally</code> block as I have done\nhere.</p>\n<p>Since the file is written multiple times in append calls, we turned off the\n<code>headers</code> option. Instead the headers will be written before the post API data\nis written.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">export</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">writeCsvHeader</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// change this if using a different CSV separator</span>\n  <span class=\"token keyword\">const</span> header <span class=\"token operator\">=</span> <span class=\"token\">POST_COLS</span><span class=\"token punctuation\">.</span><span class=\"token function\">join</span><span class=\"token punctuation\">(</span><span class=\"token string\">\",\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">writeTextFile</span><span class=\"token punctuation\">(</span><span class=\"token\">POSTS_FILE</span><span class=\"token punctuation\">,</span> <span class=\"token\"><span class=\"token string\">`</span><span class=\"token\"><span class=\"token punctuation\">${</span>header<span class=\"token punctuation\">}</span></span><span class=\"token string\">\\n</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>There's one more thing that needs to be done to the post data. The <code>body</code> field\nhas a number of newline characters in it. These will interfere with the <code>parse</code>\nfunction in the <code>encoding/csv.ts</code> module that uses newlines to separate object\nrecords. In this case, I assume that the newlines have some significance, so I\nreplace each newline in the post <code>body</code> field with a pipe character (|). When\nthe data is used, then the pipe can be converted back to a newline or into\nsomething else (an HTML break tag, for instance).</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Delete previously written file, if exists</span>\n  <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span><span class=\"token\">POSTS_FILE</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>_e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// ignore, since file probably does not exist</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// write header with object keys first</span>\n  <span class=\"token keyword\">await</span> <span class=\"token function\">writeCsvHeader</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// write data</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">let</span> i <span class=\"token operator\">=</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;=</span> <span class=\"token\">TOTAL_API_PAGES</span><span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> users <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">fetchData</span><span class=\"token punctuation\">(</span>i<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// get rid of newlines from body field</span>\n    <span class=\"token comment\">//  so that parse function in cvs module works</span>\n    <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> post <span class=\"token keyword\">of</span> users<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      post<span class=\"token punctuation\">.</span>body <span class=\"token operator\">=</span> post<span class=\"token punctuation\">.</span>body<span class=\"token punctuation\">.</span><span class=\"token function\">replaceAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"\\n\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"|\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">await</span> <span class=\"token function\">writeToFileChunk</span><span class=\"token punctuation\">(</span>users<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></pre></div><p>See\n<a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/processing_csv/populate_large_data.ts\" rel=\"noopener noreferrer\">populate_large_data.ts</a>\nin the blog source code repo for more details.</p>\n<h3 id=\"reading-data-from-a-large-csv-file\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#reading-data-from-a-large-csv-file\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Reading data from a large CSV file</h3><p>Reading data from a large CSV file involves in using the <code>Deno.open</code> function\nwith the <code>read:true</code> option.</p>\n<p>I used the <code>TextProtoReader</code> in the <code>textproto</code> module to process the CSV data\nin chunks, a line at a time. That class' constructor takes a <code>BufReader</code> object\n(<code>io/buffer</code> module) with a file object passed in.</p>\n<p><code>TextProtoReader.readline()</code> is used to read a line of the CSV file and then\n<code>parse</code> in the <code>encoding/csv</code> module is used to transform each CSV line into a\npost object.</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">readLargeCsv</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> file <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">open</span><span class=\"token punctuation\">(</span><span class=\"token\">POSTS_FILE</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> read<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> reader <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">TextProtoReader</span><span class=\"token punctuation\">(</span>BufReader<span class=\"token punctuation\">.</span><span class=\"token function\">create</span><span class=\"token punctuation\">(</span>file<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> lines <span class=\"token operator\">=</span> <span class=\"token string\">\"\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> line <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> reader<span class=\"token punctuation\">.</span><span class=\"token function\">readLine</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>line <span class=\"token operator\">===</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n    lines <span class=\"token operator\">=</span> lines <span class=\"token operator\">+</span> line <span class=\"token operator\">+</span> <span class=\"token string\">\"\\n\"</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token comment\">// Convert CSV lines into Post objects</span>\n  <span class=\"token keyword\">const</span> record <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>lines<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> skipFirstRow<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span> columns<span class=\"token operator\">:</span> <span class=\"token\">POST_COLS</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">// Display Post records</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>record<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><p>I am just printing the objects to the console here for illustration, but there\nare a lot of other things that can be done with it such as displaying the\nobjects in a UI.</p>\n<p>See\n<a href=\"https://github.com/cdoremus/deno-blog-code/blob/main/processing_csv/read_large_data.ts\" rel=\"noopener noreferrer\">read_large_data.ts</a>\nin the blog source code repo for more details.</p>\n<h2 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h2><p>This article demonstrates how to use the Deno <code>encoding/csv</code> module to convert\ndata from an object representation (like JSON) to CSV and back again. I have\nused snippets here from the\n<a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/processing_csv\" rel=\"noopener noreferrer\">source code for this article</a>,\nso check out the source to see how everything fits together. A comment at the\ntop of each source code file will tell you how to run each example.</p>\n",
            "url": "https://deno-blog.com/Processing_CSV_files_with_Deno.2022-09-20",
            "title": "Processing CSV files with Deno",
            "summary": "Processing CSV files with Deno",
            "date_modified": "2022-09-20T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/End-to-end_test_a_Deno_webapp_using_deno-puppeteer.2022-08-21",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h3 id=\"2022-08-21\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2022-08-21\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2022-08-21</h3><h5 id=\"7-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#7-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>7 min read</em></h5><h1 id=\"end-to-end-test-a-deno-webapp-using-deno-puppeteer\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#end-to-end-test-a-deno-webapp-using-deno-puppeteer\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>End-to-end test a Deno webapp using deno-puppeteer</h1><p>The <a href=\"https://github.com/lucacasonato/deno-puppeteer\" rel=\"noopener noreferrer\"><code>deno-puppeteer</code> library</a>\n-- created by Deno team member Luca Casonato -- is a Deno-compatible port of the\n<a href=\"https://pptr.dev/\" rel=\"noopener noreferrer\">puppeteer</a> Node (npm) library. The library has a number of\nuses including generating app screenshots and automating form submissions. We\nare using it here to do end-to-end (e2e) testing of a Deno webapp. The code for\nthis blog post can be found\n<a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/using_deno-puppeteer\" rel=\"noopener noreferrer\">here</a>.</p>\n<h2 id=\"app-setup\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#app-setup\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>App Setup</h2><p>Although puppeteer and deno-puppeteer can be used to e2e test any webapp, we are\ngoing to test the default <a href=\"https://fresh.deno.dev\" rel=\"noopener noreferrer\">Fresh</a> app created with this\ncommand-line (assuming <a href=\"https://deno/land\" rel=\"noopener noreferrer\">Deno</a> has been installed):</p>\n<pre><code>deno run -A -r https://fresh.deno.dev using_deno-puppeteer</code></pre><p>The new project will be found in the <code>using_deno-puppeteer</code> folder. Moving into\nthat directory, the app can be started in that folder:</p>\n<pre><code>deno task start</code></pre><p>The resulting page at <code>http://localhost:8000</code> looks like this:\n<img src=\"https://deno-blog.com/img/blog/using_deno-puppeteer/fresh_starter_page.png\" alt=\"Fresh starter page\" /></p>\n<h2 id=\"using-deno-puppeteer-for-e2e-testing\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#using-deno-puppeteer-for-e2e-testing\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Using deno-puppeteer for e2e testing</h2><h3 id=\"imports\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#imports\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Imports</h3><p>A <code>puppeteer.test.ts</code> file is created to hold the tests. Inside that file, the\ndeno-puppeteer library needs to be imported:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> puppeteer<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n  Browser<span class=\"token punctuation\">,</span>\n  Page<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/x/puppeteer@14.1.1/mod.ts\"</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>Browser</code> and <code>Page</code> classes are used in type annotations, while <code>puppeteer</code>\ndoes the library's work.</p>\n<p>We are going to use the <code>bdd</code> library in the deno_std testing module to run our\npuppeteer e2e tests. That library includes the familiar functions <code>describe</code>,\n<code>beforeAll</code>, <code>afterAll</code>, <code>beforeEach</code>, <code>afterEach</code> and <code>it</code> imported like this:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  afterAll<span class=\"token punctuation\">,</span>\n  beforeAll<span class=\"token punctuation\">,</span>\n  describe<span class=\"token punctuation\">,</span>\n  it<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/std@0.151.0/testing/bdd.ts\"</span><span class=\"token punctuation\">;</span></pre></div><p>We will also need assert functions found in the deno_std testing module to\nverify test results:</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span>\n  assert<span class=\"token punctuation\">,</span>\n  assertEquals<span class=\"token punctuation\">,</span>\n  fail<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/std@0.151.0/testing/asserts.ts\"</span><span class=\"token punctuation\">;</span></pre></div><p>Finally, we need to add the <code>readlines</code> function from the standard <code>io</code> library\nto capture subprocess stdout messages in a helper function (see below).</p>\n<div class=\"highlight\"><pre><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> readLines <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">\"https://deno.land/std@0.151.0/io/mod.ts\"</span><span class=\"token punctuation\">;</span></pre></div><h3 id=\"test-setup-and-cleanup\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#test-setup-and-cleanup\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Test setup and cleanup</h3><p>Behavior-driven development test libraries (like the popular\n<a href=\"https://jestjs.io/\" rel=\"noopener noreferrer\">Jest</a> lib) include a <code>describe</code> function to wrap a test\nsuite and <code>it</code> for test functions. Also included are <code>beforeAll</code> and <code>afterAll</code>\nfunctions used to initialize and cleanup test resources. These functions run\nbefore and after all tests are run. There are also <code>beforeEach</code> and <code>afterEach</code>\nfunctions to allow code to be run before and after each test runs (not used\nhere).</p>\n<p>Our puppeteer tests have app server (<code>server</code>), <code>Browser</code> and <code>Page</code> objects\nthat need to be declared at the top of the <code>describe</code> block since they are used\nin the `before' and 'after' functions.</p>\n<div class=\"highlight\"><pre><span class=\"token function\">describe</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"e2e tests using puppeteer: \"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">let</span> server<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> <span class=\"token function\">close</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> browser<span class=\"token operator\">:</span> Browser<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> page<span class=\"token operator\">:</span> Page<span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">beforeAll</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    server <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">startAppServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    browser <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> <span class=\"token function\">startBrowser</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    page <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> browser<span class=\"token punctuation\">.</span><span class=\"token function\">newPage</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">setViewport</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> width<span class=\"token operator\">:</span> <span class=\"token number\">400</span><span class=\"token punctuation\">,</span> height<span class=\"token operator\">:</span> <span class=\"token number\">200</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token function\">afterAll</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token operator\">?.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">await</span> browser<span class=\"token operator\">?.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">await</span> server<span class=\"token operator\">?.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token comment\">// test functions next. . .</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The app server, browser and page objects are used in all tests, so they are\ninitialized in the <code>beforeAll</code> function and closed in the <code>afterAll</code> function.\nThe <code>close</code> calls prevents the occurrence of 'leaking resources' errors. Make\nsure that the page object is always closed before the browser object.</p>\n<p>Note that the <code>server</code>, <code>browser</code> and <code>page</code> variables need type annotations to\navoid TypeScript 'implicit any' errors.</p>\n<h3 id=\"helper-functions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#helper-functions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Helper functions</h3><p>The <code>startAppServer</code> and <code>startBrowser</code> functions abstract the mechanics of\nstarting the Fresh app server that serves the webapp and the puppeteer browser\nwhich we will run in headless mode to allow the tests to run in a CI/CD\nenvironment.</p>\n<div class=\"highlight\"><pre><span class=\"token comment\">/**\n * Run the Fresh server locally.\n *\n * @returns {{close: () =&gt; void}}: A handle to the server allowing closing of the server sub-process and\n * stdout/stderr within that.\n */</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">startAppServer</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span><span class=\"token punctuation\">{</span> <span class=\"token function\">close</span><span class=\"token operator\">:</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token keyword\">void</span> <span class=\"token punctuation\">}</span><span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> serverProcess <span class=\"token operator\">=</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">run</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// Fresh command line without the wait flag</span>\n    cmd<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span>Deno<span class=\"token punctuation\">.</span><span class=\"token function\">execPath</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"run\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"-A\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"dev.ts\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n    cwd<span class=\"token operator\">:</span> Deno<span class=\"token punctuation\">.</span><span class=\"token function\">cwd</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    stdout<span class=\"token operator\">:</span> <span class=\"token string\">\"piped\"</span><span class=\"token punctuation\">,</span>\n    stderr<span class=\"token operator\">:</span> <span class=\"token string\">\"piped\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Waiting for server to start...\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">//  Display some stdout messages.</span>\n  <span class=\"token keyword\">for</span> <span class=\"token keyword\">await</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">const</span> line <span class=\"token keyword\">of</span> <span class=\"token function\">readLines</span><span class=\"token punctuation\">(</span>serverProcess<span class=\"token punctuation\">.</span>stdout<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>line<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Listening on http\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token\">console</span><span class=\"token punctuation\">.</span><span class=\"token function\">log</span><span class=\"token punctuation\">(</span>line<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">break</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">async</span> <span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">await</span> serverProcess<span class=\"token punctuation\">.</span>stdout<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">await</span> serverProcess<span class=\"token punctuation\">.</span>stderr<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token keyword\">await</span> serverProcess<span class=\"token punctuation\">.</span><span class=\"token function\">close</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n<span class=\"token comment\">/**\n * Start the puppeteer browser in headless mode\n *\n * @returns {Promise&lt;Browser&gt;}: Resolves to a puppeteer Browser instance\n */</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">async</span> <span class=\"token keyword\">function</span> <span class=\"token function\">startBrowser</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> <span class=\"token\">Promise</span><span class=\"token operator\">&lt;</span>Browser<span class=\"token operator\">&gt;</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> browser<span class=\"token operator\">:</span> Browser <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> puppeteer<span class=\"token punctuation\">.</span><span class=\"token function\">launch</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    headless<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n    args<span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token string\">\"--no-sandbox\"</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> browser<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></pre></div><h3 id=\"test-functions\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#test-functions\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Test functions</h3><p>Individual tests are found in <code>it</code> functions:</p>\n<div class=\"highlight\"><pre><span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  name<span class=\"token operator\">:</span> <span class=\"token string\">\"should display welcome message\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token function\">fn</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">goto</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http://localhost:8000/\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n      waitUntil<span class=\"token operator\">:</span> <span class=\"token string\">\"networkidle2\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">const</span> selection <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">waitForSelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"body &gt; div &gt; p\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>selection<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token operator\">?.</span><span class=\"token function\">evaluate</span><span class=\"token punctuation\">(</span>\n        <span class=\"token punctuation\">(</span>element<span class=\"token operator\">:</span> HTMLElement<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> element<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">,</span>\n        selection<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>text<span class=\"token operator\">?.</span><span class=\"token function\">startsWith</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Welcome\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">fail</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">ERROR: Selector not found</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>Once the <code>page.goto</code> function runs and navigates to the page to be tested. All\ntests in this file work on the same page. The <code>page.waitForSelector</code> is called\nwith a CSS selector pointing to the DOM node on the page to be verified. In this\ncase, we are verifying that a message that starts with 'Welcome' exists at that\nspot. We use the <code>page.evaluate</code> to obtain the text content (i.e. the message)\nfrom the HTMLElement.</p>\n<p>The function of the counter component is tested in the next two test functions.\nWe want to know that the increment (+1) and decrement (-1) buttons work\nproperly.</p>\n<div class=\"highlight\"><pre><span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  name<span class=\"token operator\">:</span> <span class=\"token string\">\"should decrement counter\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token function\">fn</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">goto</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http://localhost:8000/\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n      waitUntil<span class=\"token operator\">:</span> <span class=\"token string\">\"networkidle2\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// TODO: Get counter text for later decrement comparison</span>\n    <span class=\"token comment\">// click -1 button</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"body &gt; div &gt; div &gt; button:nth-child(2)\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// check to see if counter has decremented</span>\n    <span class=\"token keyword\">const</span> selection <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">waitForSelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"body &gt; div &gt; div &gt; p\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>selection<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">evaluate</span><span class=\"token punctuation\">(</span>\n        <span class=\"token punctuation\">(</span>element<span class=\"token operator\">:</span> HTMLElement<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> element<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">,</span>\n        selection<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token comment\">// counter should be 2 now</span>\n      <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">,</span> <span class=\"token string\">\"2\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">fail</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">ERROR: Selector not found</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token function\">it</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  name<span class=\"token operator\">:</span> <span class=\"token string\">\"should increment counter\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token function\">fn</span><span class=\"token operator\">:</span> <span class=\"token keyword\">async</span> <span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">goto</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"http://localhost:8000/\"</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n      waitUntil<span class=\"token operator\">:</span> <span class=\"token string\">\"networkidle2\"</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// TODO: Get counter text for later increment comparison</span>\n    <span class=\"token comment\">// click +1 button</span>\n    <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">click</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"body &gt; div &gt; div &gt; button:nth-child(3)\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token comment\">// check to see if counter has decremented</span>\n    <span class=\"token keyword\">const</span> selection <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">waitForSelector</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"body &gt; div &gt; div &gt; p\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>selection<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> text <span class=\"token operator\">=</span> <span class=\"token keyword\">await</span> page<span class=\"token punctuation\">.</span><span class=\"token function\">evaluate</span><span class=\"token punctuation\">(</span>\n        <span class=\"token punctuation\">(</span>element<span class=\"token operator\">:</span> HTMLElement<span class=\"token punctuation\">)</span> <span class=\"token operator\">=&gt;</span> element<span class=\"token punctuation\">.</span>textContent<span class=\"token punctuation\">,</span>\n        selection<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token comment\">// counter should be 4 now</span>\n      <span class=\"token function\">assertEquals</span><span class=\"token punctuation\">(</span>text<span class=\"token punctuation\">,</span> <span class=\"token string\">\"4\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">fail</span><span class=\"token punctuation\">(</span><span class=\"token\"><span class=\"token string\">`</span><span class=\"token string\">ERROR: Selector not found</span><span class=\"token string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></pre></div><p>The <code>page.click</code> function is used to click a button found at the DOM node\ndefined by the CSS selector. Once the button is clicked, the DOM node containing\nthe counter's value is found and verified to have been incremented or\ndecremented (note that the Fresh app sets the counter's start value to 3).</p>\n<h3 id=\"test-output\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#test-output\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Test output</h3><p>The command line to run all tests (<code>deno test --no-check-A</code>) has been\nencapsulated in the <code>deno.json</code> file as task 'test'. You run that with\n<code>deno task test</code>. When that is done, the following output should appear:</p>\n<pre><code>$&gt; deno task test\nWarning deno task is unstable and may drastically change in the future\nTask test deno test --no-check -A\nrunning 1 test from ./puppeteer.test.ts\nPuppeteer e2e testing...  ...\n------- output -------\nWaiting for server to start...\nListening on http://localhost:8000/\n----- output end -----\n  should display welcome message ... ok (1s)\n  should decrement counter ... ok (1s)\n  should increment counter ... ok (1s)\nPuppeteer e2e testing...  ... ok (4s)\n\nok | 1 passed (3 steps) | 0 failed (5s)</code></pre><h3 id=\"puppeteer-api\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#puppeteer-api\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Puppeteer API</h3><p>There are a number of functions found in the\n<a href=\"https://pptr.dev/api/\" rel=\"noopener noreferrer\">Puppeteer API</a> that I have not covered here. Here are\nfew of the highlights:</p>\n<ul>\n<li><code>Page.select</code> to target a drop-down or select element.</li>\n<li><code>Page.type</code> to type text into an input box.</li>\n<li><code>Page.waitForFunction</code> waits for a function to finish evaluating.</li>\n<li><code>Page.screenshot</code> to take a screenshot of the current page.</li>\n<li><code>Page.pdf</code> to create a pdf of the current page.</li>\n</ul>\n<h3 id=\"conclusion\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#conclusion\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>Conclusion</h3><p>As stated previously, complete source code for the example in this article is\navailable\n<a href=\"https://github.com/cdoremus/deno-blog-code/tree/main/using_deno-puppeteer\" rel=\"noopener noreferrer\">here</a>.\nPlease note that the current versions of the libraries used in this example will\nchange with time.</p>\n<p>While I used Fresh for this work, I learned how to use deno-puppeteer when I\ncreated e2e tests for the <a href=\"https://ultrajs.dev\" rel=\"noopener noreferrer\">Ultra</a> full-stack Deno\nframework. I want to thank Omar Mashaal and James Edmonds for their assistance\nin this effort.</p>\n<p>I'd also like to thank Kyle June for reviewing previous versions of this post.</p>\n<p>The deno-puppeteer library can now be combined with Deno's build-in testing\nmodules to allow the full\n<a href=\"https://martinfowler.com/articles/practical-test-pyramid.html\" rel=\"noopener noreferrer\">testing pyramid</a>\nto be done in a Deno-native way.</p>\n<h3 id=\"references\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#references\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>References</h3><ul>\n<li><a href=\"https://pptr.dev/api/\" rel=\"noopener noreferrer\">Puppeteer API Docs</a></li>\n<li><a href=\"https://github.com/lucacasonato/deno-puppeteer\" rel=\"noopener noreferrer\">deno-puppeteer library</a></li>\n<li><a href=\"https://goodguydaniel.com/blog/tips-end-to-end-testing-puppeteer\" rel=\"noopener noreferrer\">Tips for End to End Testing with Puppeteer</a></li>\n</ul>\n",
            "url": "https://deno-blog.com/End-to-end_test_a_Deno_webapp_using_deno-puppeteer.2022-08-21",
            "title": "End-to-end test a Deno webapp using deno-puppeteer",
            "summary": "End-to-end test a Deno webapp using deno-puppeteer",
            "date_modified": "2022-08-21T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        },
        {
            "id": "https://deno-blog.com/How_I_made_this_blog.2022-08-07",
            "content_html": "<style>:root,[data-color-mode=light][data-light-theme=light],[data-color-mode=dark][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=light]{--color-canvas-default-transparent:rgba(255,255,255,0);--color-prettylights-syntax-comment:#6e7781;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-entity:#8250df;--color-prettylights-syntax-storage-modifier-import:#24292f;--color-prettylights-syntax-entity-tag:#116329;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-fg-default:#24292f;--color-fg-muted:#57606a;--color-canvas-default:#fff;--color-canvas-subtle:#f6f8fa;--color-border-default:#d0d7de;--color-border-muted:#d8dee4;--color-neutral-muted:rgba(175,184,193,.2);--color-accent-fg:#0969da;--color-accent-emphasis:#0969da;--color-danger-fg:#cf222e}}[data-color-mode=light][data-light-theme=dark],[data-color-mode=dark][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}@media (prefers-color-scheme:light){[data-color-mode=auto][data-light-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}@media (prefers-color-scheme:dark){[data-color-mode=auto][data-dark-theme=dark]{--color-canvas-default-transparent:rgba(13,17,23,0);--color-prettylights-syntax-comment:#8b949e;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#c9d1d9;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-fg-default:#c9d1d9;--color-fg-muted:#8b949e;--color-canvas-default:#0d1117;--color-canvas-subtle:#161b22;--color-border-default:#30363d;--color-border-muted:#21262d;--color-neutral-muted:rgba(110,118,129,.4);--color-accent-fg:#58a6ff;--color-accent-emphasis:#1f6feb;--color-danger-fg:#f85149}}.markdown-body{word-wrap:break-word;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;font-size:16px;line-height:1.5}.markdown-body:before{content:\"\";display:table}.markdown-body:after{clear:both;content:\"\";display:table}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--color-danger-fg)}.markdown-body .anchor{float:left;margin-left:-20px;padding-right:4px;line-height:1}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre,.markdown-body details{margin-top:0;margin-bottom:16px}.markdown-body hr{height:.25em;background-color:var(--color-border-default);border:0;margin:24px 0;padding:0}.markdown-body blockquote{color:var(--color-fg-muted);border-left:.25em solid var(--color-border-default);padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:24px;margin-bottom:16px;font-weight:600;line-height:1.25}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--color-fg-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit;padding:0 .2em}.markdown-body h1{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:2em}.markdown-body h2{border-bottom:1px solid var(--color-border-muted);padding-bottom:.3em;font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:var(--color-fg-muted);font-size:.85em}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{border-bottom:0;padding-bottom:0}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ol[type=\"1\"]{list-style-type:decimal}.markdown-body ol[type=a]{list-style-type:lower-alpha}.markdown-body ol[type=i]{list-style-type:lower-roman}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{margin-top:16px;padding:0;font-size:1em;font-style:italic;font-weight:600}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{width:100%;width:-webkit-max-content;width:-webkit-max-content;width:max-content;max-width:100%;display:block;overflow:auto}.markdown-body table th{font-weight:600}.markdown-body table th,.markdown-body table td{border:1px solid var(--color-border-default);padding:6px 13px}.markdown-body table tr{background-color:var(--color-canvas-default);border-top:1px solid var(--color-border-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--color-canvas-subtle)}.markdown-body table img{background-color:rgba(0,0,0,0)}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:var(--color-canvas-default)}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:rgba(0,0,0,0)}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{float:left;width:auto;border:1px solid var(--color-border-default);margin:13px 0 0;padding:7px;display:block;overflow:hidden}.markdown-body span.frame span img{float:left;display:block}.markdown-body span.frame span span{clear:both;color:var(--color-fg-default);padding:5px 0 0;display:block}.markdown-body span.align-center{clear:both;display:block;overflow:hidden}.markdown-body span.align-center>span{text-align:center;margin:13px auto 0;display:block;overflow:hidden}.markdown-body span.align-center span img{text-align:center;margin:0 auto}.markdown-body span.align-right{clear:both;display:block;overflow:hidden}.markdown-body span.align-right>span{text-align:right;margin:13px 0 0;display:block;overflow:hidden}.markdown-body span.align-right span img{text-align:right;margin:0}.markdown-body span.float-left{float:left;margin-right:13px;display:block;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{float:right;margin-left:13px;display:block;overflow:hidden}.markdown-body span.float-right>span{text-align:right;margin:13px auto 0;display:block;overflow:hidden}.markdown-body code,.markdown-body tt{background-color:var(--color-neutral-muted);border-radius:6px;margin:0;padding:.2em .4em;font-size:85%}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre{word-wrap:normal}.markdown-body pre code{font-size:100%}.markdown-body pre>code{word-break:normal;white-space:pre;background:0 0;border:0;margin:0;padding:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{word-break:normal;margin-bottom:0}.markdown-body .highlight pre,.markdown-body pre{background-color:var(--color-canvas-subtle);border-radius:6px;padding:16px;font-size:85%;line-height:1.45;overflow:auto}.markdown-body pre code,.markdown-body pre tt{max-width:auto;line-height:inherit;word-wrap:normal;background-color:rgba(0,0,0,0);border:0;margin:0;padding:0;display:inline;overflow:visible}.markdown-body .csv-data td,.markdown-body .csv-data th{text-align:left;white-space:nowrap;padding:5px;font-size:12px;line-height:1;overflow:hidden}.markdown-body .csv-data .blob-num{text-align:right;background:var(--color-canvas-default);border:0;padding:10px 8px 9px}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{background:var(--color-canvas-subtle);border-top:0;font-weight:600}.markdown-body [data-footnote-ref]:before{content:\"[\"}.markdown-body [data-footnote-ref]:after{content:\"]\"}.markdown-body .footnotes{color:var(--color-fg-muted);border-top:1px solid var(--color-border-default);font-size:12px}.markdown-body .footnotes ol{padding-left:16px}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target:before{pointer-events:none;content:\"\";border:2px solid var(--color-accent-emphasis);border-radius:6px;position:absolute;top:-8px;bottom:-8px;left:-24px;right:-8px}.markdown-body .footnotes li:target{color:var(--color-fg-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body{background-color:var(--color-canvas-default);color:var(--color-fg-default)}.markdown-body a{color:var(--color-accent-fg);text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body iframe{background-color:#fff;border:0;margin-bottom:16px}.markdown-body svg.octicon{fill:currentColor}.markdown-body .anchor>.octicon{display:inline}.markdown-body .highlight .token.keyword,.gfm-highlight .token.keyword{color:var(--color-prettylights-syntax-keyword)}.markdown-body .highlight .token.tag .token.class-name,.markdown-body .highlight .token.tag .token.script .token.punctuation,.gfm-highlight .token.tag .token.class-name,.gfm-highlight .token.tag .token.script .token.punctuation{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .highlight .token.operator,.markdown-body .highlight .token.number,.markdown-body .highlight .token.boolean,.markdown-body .highlight .token.tag .token.punctuation,.markdown-body .highlight .token.tag .token.script .token.script-punctuation,.markdown-body .highlight .token.tag .token.attr-name,.gfm-highlight .token.operator,.gfm-highlight .token.number,.gfm-highlight .token.boolean,.gfm-highlight .token.tag .token.punctuation,.gfm-highlight .token.tag .token.script .token.script-punctuation,.gfm-highlight .token.tag .token.attr-name{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.function,.gfm-highlight .token.function{color:var(--color-prettylights-syntax-entity)}.markdown-body .highlight .token.string,.gfm-highlight .token.string{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.comment,.gfm-highlight .token.comment{color:var(--color-prettylights-syntax-comment)}.markdown-body .highlight .token.class-name,.gfm-highlight .token.class-name{color:var(--color-prettylights-syntax-variable)}.markdown-body .highlight .token.regex,.gfm-highlight .token.regex{color:var(--color-prettylights-syntax-string)}.markdown-body .highlight .token.regex .regex-delimiter,.gfm-highlight .token.regex .regex-delimiter{color:var(--color-prettylights-syntax-constant)}.markdown-body .highlight .token.tag .token.tag,.markdown-body .highlight .token.property,.gfm-highlight .token.tag .token.tag,.gfm-highlight .token.property{color:var(--color-prettylights-syntax-entity-tag)}</style>\n<h4 id=\"2020-08-07\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2020-08-07\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>2020-08-07</h4><h5 id=\"2-min-read\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#2-min-read\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a><em>2 min read</em></h5><h1 id=\"how-i-made-this-blog\"><a class=\"anchor\" aria-hidden=\"true\" tabindex=\"-1\" href=\"#how-i-made-this-blog\"><svg class=\"octicon octicon-link\" viewbox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z\"></path></svg></a>How I made this blog</h1><p>Welcome to my blog! I'll be blogging about the JavaScript runtime\n<a href=\"https://deno.land\" rel=\"noopener noreferrer\">Deno</a>. I have been closely following Deno since it's 1.0\nrelease, but I got really excited when the framework\n<a href=\"https://fresh.deno.dev\" rel=\"noopener noreferrer\">Fresh</a> was recently released to enable full-stack Deno\nsupported by the Deno core team. Rather than just reporting on Deno news, this\nblog will focus on articles that demonstrate how to use Deno technologies and\nESM third-party libraries.</p>\n<p>This blog is built using Fresh. Fresh uses Preact under the covers and follows\nrouting conventions popularized by NextJS. Fresh does not deploy JavaScript by\ndefault but allows for JS interactivity if needed (it was not needed at this\ntime). <a href=\"https://twind.dev/\" rel=\"noopener noreferrer\">Twind</a>, a runtime compiler for\n<a href=\"https://tailwindcss.com/\" rel=\"noopener noreferrer\">Tailwind</a>, was used to style the site with help from\na utility provided by the Fresh devs (<code>util/twind.ts</code>). I'm sure you'll see a\nlot more about Fresh in future blog posts.</p>\n<p>At this point the blog is a simple app that just iterates though files written\nin Markdown stored in a <code>posts</code> folder. I'm using the\n<a href=\"https://deno.land/x/gfm\" rel=\"noopener noreferrer\">gfm</a> library to render the Markdown posts. I intend to\ngo into more details on how this is done in a future article.</p>\n<p><a href=\"https://deno.com/deploy\" rel=\"noopener noreferrer\">Deno Deploy</a> is used to deploy this blog. Deno\nDeploy's\n<a href=\"https://deno.com/deploy/docs/deployments#git-integration\" rel=\"noopener noreferrer\">github integration</a>\nmakes easy work of deploying a github repo to Deploy's edge platform.</p>\n<p>I'm hoping to evolve the blog with new features and improved code quality as\ntime goes on. Source code can be found in this repo:\n<a href=\"https://github.com/cdoremus/fresh-blog\" rel=\"noopener noreferrer\">https://github.com/cdoremus/fresh-blog</a>.</p>\n<p>Please reach out to me on Twitter at <a href=\"https://twitter.com/cdoremus\" rel=\"noopener noreferrer\">@cdoremus</a>\nto offer suggestions and ideas for this blog.</p>\n",
            "url": "https://deno-blog.com/How_I_made_this_blog.2022-08-07",
            "title": "How I made this blog",
            "summary": "How I made this blog",
            "date_modified": "2022-08-07T00:00:00.000Z",
            "author": {
                "name": "Craig Doremus"
            }
        }
    ]
}