<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jason Shin</title>
    <description>The latest articles on DEV Community by Jason Shin (@jasonshin).</description>
    <link>https://dev.to/jasonshin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F166695%2F9b00a9fb-aed1-4301-ad5b-69e34d720be5.jpeg</url>
      <title>DEV Community: Jason Shin</title>
      <link>https://dev.to/jasonshin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jasonshin"/>
    <language>en</language>
    <item>
      <title>[sqlx-ts] compile-time checked queries without DSL in Typescript</title>
      <dc:creator>Jason Shin</dc:creator>
      <pubDate>Mon, 24 Jul 2023 10:56:28 +0000</pubDate>
      <link>https://dev.to/jasonshin/sqlx-ts-free-nodejs-developers-from-orms-3a7a</link>
      <guid>https://dev.to/jasonshin/sqlx-ts-free-nodejs-developers-from-orms-3a7a</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;[&lt;a href="https://github.com/JasonShin/sqlx-ts" rel="noopener noreferrer"&gt;Github&lt;/a&gt; | &lt;a href="https://github.com/JasonShin/sqlx-ts-demo" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;This story is about &lt;a href="https://github.com/JasonShin/sqlx-ts" rel="noopener noreferrer"&gt;sqlx-ts&lt;/a&gt;. It is a CLI tool that provides&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compile-time checked&lt;/strong&gt; queries in your TypeScript code&lt;/li&gt;
&lt;li&gt;TypeScript &lt;strong&gt;type generation&lt;/strong&gt; against those SQLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database agnostic&lt;/strong&gt; API&lt;/li&gt;
&lt;li&gt;Work with both Typescript and Javascript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, it enables you to never ship broken and unoptimized queries to production. Here is an example of sqlx-ts running against PostgreSQL (but it works with MySQL as well)&lt;/p&gt;


  


&lt;p&gt;in the video, sqlx-ts performed&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://jasonshin.github.io/sqlx-ts/reference-guide/1.sql-check.html" rel="noopener noreferrer"&gt;SQL check&lt;/a&gt; to check if the SQL is compatible with PG&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jasonshin.github.io/sqlx-ts/reference-guide/4.typescript-types-generation.html" rel="noopener noreferrer"&gt;Generate Typescript interfaces&lt;/a&gt; so you can use the type to keep your code type-safe&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result, you can never deploy SQL queries that are broken or unoptimized. Optimizing query is easier with raw SQLs as you can directly run SQL analyzer instead of grabbing a generated SQLs from ORMs.&lt;/p&gt;

&lt;p&gt;sqlx-ts can work with complex queries as well such as joins&lt;/p&gt;


  


&lt;h2&gt;
  
  
  Design
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2j3Rjd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2j3Rjd2.png"&gt;&lt;/a&gt;&lt;br&gt;
sqlx-ts core is written in &lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;. This enables a few key fundamentals&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/JasonShin/sqlx-ts/blob/main/src/ts_generator/sql_parser/expressions/translate_expr.rs" rel="noopener noreferrer"&gt;pattern matching&lt;/a&gt; to never miss on any AST pattern (when dealing with SQL and Typescript)&lt;/li&gt;
&lt;li&gt;top-notch performance using &lt;a href="https://doc.rust-lang.org/book/ch16-01-threads.html" rel="noopener noreferrer"&gt;multi-threading&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;For a full guide, &lt;a href="https://jasonshin.github.io/sqlx-ts/user-guide/1.installation.html" rel="noopener noreferrer"&gt;check the official documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, install sqlx-ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;sqlx-ts
&lt;span class="c"&gt;# or if yarn&lt;/span&gt;
yarn add sqlx-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;installing sqlx-ts NPM module also installs Rust binary of sqlx-ts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jasonshin.github.io/sqlx-ts/user-guide/2.getting-started.html" rel="noopener noreferrer"&gt;Check the full getting started guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check &lt;code&gt;--help&lt;/code&gt; for all supported CLI arguments&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx sqlx-ts &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to have a database with schema running locally (&lt;a href="https://github.com/JasonShin/sqlx-ts/blob/main/docker-compose.yml" rel="noopener noreferrer"&gt;for example&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npx sqlx-ts &amp;lt;path to project&amp;gt; &lt;span class="nt"&gt;--db-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="nt"&gt;--db-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4321 &lt;span class="nt"&gt;--db-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--db-pass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--db-user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;--db-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlx-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above command will search the target directory and validate all detected SQLs in-code against the target database's &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema.html" rel="noopener noreferrer"&gt;information_schema&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's it! All your queries are valid if sqlx-ts did not throw an error.&lt;/p&gt;

&lt;p&gt;Next, we can try generating TypeScript types against the detected SQLs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/index.ts&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`
        SELECT items.id
        FROM items
    `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;someQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tableId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;npx sqlx-ts &amp;lt;path to project&amp;gt; --db-host=127.0.0.1 --db-port=4321 --db-type=postgres --db-pass=postgres --db-user=postgres --db-name=sqlx-ts -g&lt;/code&gt; *note that I added &lt;code&gt;-g&lt;/code&gt; in the end.&lt;/p&gt;

&lt;p&gt;The command generated&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/index.queries.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SomeQueryParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ISomeQueryResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;food_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;table_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;time_takes_to_cook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ISomeQueryQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SomeQueryParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ISomeQueryResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you don't like the generated type's formatting, you can simply post-process them using &lt;a href="https://github.com/JasonShin/sqlx-ts-demo/blob/master/package.json#L9" rel="noopener noreferrer"&gt;prettier&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Now you can use the generated types against the database queries in-code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ISomeQueryResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.queries&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;someQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ISomeQueryResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`
        SELECT items.id
        FROM items
    `&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;someQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tableId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, any changes in the schema would break the query during build-time.&lt;/p&gt;

&lt;p&gt;You can also use &lt;a href="https://jasonshin.github.io/sqlx-ts/reference-guide/4.5.annotations.html" rel="noopener noreferrer"&gt;annotations&lt;/a&gt; to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;override database target&lt;/li&gt;
&lt;li&gt;provide custom name for queries&lt;/li&gt;
&lt;li&gt;override generated types&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Supported features
&lt;/h2&gt;

&lt;p&gt;You can imagine 95% of SQL features are supported for both SQL check and type-generation features.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;feature&lt;/th&gt;
&lt;th&gt;SQL check&lt;/th&gt;
&lt;th&gt;TS generation&lt;/th&gt;
&lt;th&gt;Future Roadmap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SELECT&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JOINs&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;enums&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;td&gt;Will be supported in the near future&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL analyzer&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Will be supported in the near future as this will be a unique feature that sqlx-ts provides&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process .sql files&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Will be added soon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Motivations
&lt;/h2&gt;

&lt;p&gt;The project is largely inspired by a library in Rust sqlx. I'm always interested in how to keep code safe especially during compile time. Rust and its ecosystem always have something to learn and port over into TypeScript world for greater good.&lt;/p&gt;

&lt;p&gt;Thank you and please &lt;a href="https://github.com/JasonShin/sqlx-ts/issues" rel="noopener noreferrer"&gt;report any issues on Github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>sql</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
