<?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: Jan Kleinert</title>
    <description>The latest articles on DEV Community by Jan Kleinert (@jankleinert).</description>
    <link>https://dev.to/jankleinert</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%2F197908%2Febc03a7b-25df-466e-9c3f-e75ddc979c4e.jpg</url>
      <title>DEV Community: Jan Kleinert</title>
      <link>https://dev.to/jankleinert</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jankleinert"/>
    <language>en</language>
    <item>
      <title>Browse and query Cloud Spanner databases from Visual Studio Code</title>
      <dc:creator>Jan Kleinert</dc:creator>
      <pubDate>Wed, 03 Nov 2021 16:18:21 +0000</pubDate>
      <link>https://dev.to/jankleinert/browse-and-query-cloud-spanner-databases-from-visual-studio-code-2f0e</link>
      <guid>https://dev.to/jankleinert/browse-and-query-cloud-spanner-databases-from-visual-studio-code-2f0e</guid>
      <description>&lt;p&gt;&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; is one of the most widely-used IDEs, due in part to the variety of extensions that are available to developers. For developers who are building applications that interact with &lt;a href="https://cloud.google.com/spanner" rel="noopener noreferrer"&gt;Cloud Spanner&lt;/a&gt;, we're excited to announce the &lt;a href="https://marketplace.visualstudio.com/items?itemName=google-cloud-spanner-ecosystem.google-cloud-spanner-driver" rel="noopener noreferrer"&gt;Google Cloud Spanner driver&lt;/a&gt; for the popular &lt;a href="https://vscode-sqltools.mteixeira.dev/" rel="noopener noreferrer"&gt;SQLTools extension&lt;/a&gt; for VS Code. &lt;/p&gt;

&lt;p&gt;The SQLTools extension works with a variety of SQL drivers and allows developers to manage database connections, execute and generate queries, and more from within VS Code. By using the Cloud Spanner driver with SQLTools, developers can browse tables and execute queries, DDL statements, and DML statements on Cloud Spanner databases without having to leave the IDE.&lt;/p&gt;

&lt;p&gt;In this post, we'll walk through the process of installing the extension, connecting to a Cloud Spanner database, and using SQLTools with the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you get started, you'll need to have a &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener noreferrer"&gt;Google Cloud Platform project&lt;/a&gt; with a Cloud Spanner instance and a database. This &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-spanner-first-db" rel="noopener noreferrer"&gt;codelab&lt;/a&gt; will walk you through the process if you haven't used Cloud Spanner before. Alternatively, you can use the &lt;a href="https://cloud.google.com/spanner/docs/emulator" rel="noopener noreferrer"&gt;emulator&lt;/a&gt;. You'll also need to have &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; installed on your computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;To install the Cloud Spanner driver for SQLTools, click on the Extensions icon in VS Code, search for "cloud spanner driver", and install the extension called Google Cloud Spanner Driver. Alternatively, you can install the Cloud Spanner driver for SQLTools from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=google-cloud-spanner-ecosystem.google-cloud-spanner-driver" rel="noopener noreferrer"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Fextensions_marketplace.max-500x500.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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Fextensions_marketplace.max-500x500.png" alt="extensions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the extension is installed, you'll see a database icon for SQLTools, as highlighted by the red rectangle in the image below, show up in VS Code. Click this database icon to access the extension.&lt;/p&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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2FScreen_Shot_2021-04-01_at_10.29.04_AM.max-400x400.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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2FScreen_Shot_2021-04-01_at_10.29.04_AM.max-400x400.png" alt="choose"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to a Cloud Spanner database
&lt;/h2&gt;

&lt;p&gt;With the extension installed, click the Add New Connection icon in SQLTools to open the Connection Assistant and choose Google Cloud Spanner Driver. You can connect either to a Spanner instance on Google Cloud or to an emulator instance. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring a connection using the emulator
&lt;/h2&gt;

&lt;p&gt;The Cloud SDK offers a local, in-memory &lt;a href="https://cloud.google.com/spanner/docs/emulator" rel="noopener noreferrer"&gt;emulator&lt;/a&gt; that you can use while developing and testing. To use the SQLTools extension with the emulator, you must first start the emulator. Then, in the Connection Settings step, enter values for Connection name, Google Cloud Project ID, Spanner Instance ID, and Spanner Database ID. Select the checkbox next to Connect to emulator. When you use this setting, the instance and database you specified will be automatically created for you in the emulator if they do not already exist.&lt;/p&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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Funnamed_5_7MGdvpb.max-600x600.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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Funnamed_5_7MGdvpb.max-600x600.png" alt="connection assistant"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring a connection to a Cloud Spanner database on Google Cloud
&lt;/h2&gt;

&lt;p&gt;If you are connecting to a Cloud Spanner database running on Google Cloud, you'll need to provide the Google Cloud Project ID, Spanner Instance ID, and Spanner Database ID. You can enter any value you like for the Connection name. You'll also need to specify your credentials in one of two ways: enter the absolute path to your credential key file in the Connection Assistant or set the &lt;code&gt;GOOGLE_ACCOUNT_CREDENTIALS&lt;/code&gt; environment variable to the path to your credential key file. If you are using the &lt;code&gt;GOOGLE_ACCOUNT_CREDENTIALS&lt;/code&gt; environment variable, note that if VS Code was already running before you set the environment variable, then you will need to restart VS Code. Your service account will need to be granted appropriate permissions for interacting with Cloud Spanner. For more information about credentials, see the documentation on creating &lt;a href="https://cloud.google.com/iam/docs/creating-managing-service-accounts" rel="noopener noreferrer"&gt;service accounts&lt;/a&gt; and &lt;a href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys" rel="noopener noreferrer"&gt;service account keys&lt;/a&gt;. You can find a list of Cloud Spanner roles in &lt;a href="https://cloud.google.com/iam/docs/understanding-roles#cloud-spanner-roles" rel="noopener noreferrer"&gt;this table&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and establishing connections
&lt;/h2&gt;

&lt;p&gt;Once you've entered the connection settings information, you can click TEST CONNECTION to make sure the connection is successful, and then click SAVE CONNECTION.&lt;/p&gt;

&lt;p&gt;On the final step of the Connection Assistant, click CONNECT NOW.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browsing database tables
&lt;/h2&gt;

&lt;p&gt;In the Connections section of SQLTools, you can view the tables in your database. In the screenshot below, you can see the columns in the comments table.&lt;/p&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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Funnamed_6_5zLZJIK.max-500x500.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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2Funnamed_6_5zLZJIK.max-500x500.png" alt="browsing"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Right-clicking on a table name provides options such as showing table records or generating an insert query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Executing queries and statements
&lt;/h2&gt;

&lt;p&gt;The Cloud Spanner driver supports executing queries, DDL statements, and &lt;a href="https://cloud.google.com/spanner/docs/dml-tasks" rel="noopener noreferrer"&gt;DML statements&lt;/a&gt;. If you execute multiple statements in a single script, each statement will be executed in a separate transaction. Note that the extension is intended for use during development and testing, not for administration of production database environments.&lt;/p&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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2FScreen_Shot_2021-04-01_at_11.38.45_AM.max-600x600.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%2Fstorage.googleapis.com%2Fgweb-cloudblog-publish%2Fimages%2FScreen_Shot_2021-04-01_at_11.38.45_AM.max-600x600.png" alt="db environments"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Queries use single-use read-only transactions, while DML statements use read-write transactions. Make sure that the service account you're using has the necessary permissions to execute the queries or statements. For more information on types of transactions, see &lt;a href="https://cloud.google.com/spanner/docs/transactions" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Interacting with your Cloud Spanner databases from within your IDE can make your development process more efficient and reduce the need to switch between multiple tools and interfaces. Ready to try it yourself? Install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=google-cloud-spanner-ecosystem.google-cloud-spanner-driver" rel="noopener noreferrer"&gt;Cloud Spanner driver for SQLTools&lt;/a&gt; and start exploring and interacting with your Cloud Spanner databases from within VS Code. If you have suggestions or issues, you can raise them in the &lt;a href="https://github.com/cloudspannerecosystem/sqltools-cloud-spanner-driver/issues" rel="noopener noreferrer"&gt;issue tracker&lt;/a&gt; or for questions or comments, feel free to reach out to me on &lt;a href="https://twitter.com/jankleinert" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. We would love to hear your feedback.&lt;/p&gt;

</description>
      <category>spanner</category>
      <category>database</category>
      <category>vscode</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Enable query tagging with Sqlcommenter to understand application impact on database performance</title>
      <dc:creator>Jan Kleinert</dc:creator>
      <pubDate>Wed, 03 Nov 2021 15:47:39 +0000</pubDate>
      <link>https://dev.to/jankleinert/enable-query-tagging-with-sqlcommenter-to-understand-application-impact-on-database-performance-5d63</link>
      <guid>https://dev.to/jankleinert/enable-query-tagging-with-sqlcommenter-to-understand-application-impact-on-database-performance-5d63</guid>
      <description>&lt;p&gt;Cloud SQL Insights &lt;a href="https://cloud.google.com/blog/products/databases/get-ahead-of-database-performance-issues-with-cloud-sql-insights" rel="noopener noreferrer"&gt;launched&lt;/a&gt; earlier this year, giving developers a tool that helps detect, diagnose, and prevent query performance problems for PostgreSQL for Cloud SQL databases. Out of the box, the &lt;a href="https://cloud.google.com/sql/docs/postgres/query-insights-overview" rel="noopener noreferrer"&gt;Query Insights&lt;/a&gt; dashboard provides database load graphs, detailed query performance, and built-in query plans. Users can boost the power of Insights by enabling query tagging via an open source library called Sqlcommenter. In this post, we'll share the benefits of using query tagging, what Sqlcommenter is, and two ways you can enable it: using a supported ORM or manually by following the Sqlcommenter spec.&lt;/p&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aqzwb1lc7qcqsysvkst.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aqzwb1lc7qcqsysvkst.png" alt="Query Insights Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Application-centric database monitoring
&lt;/h2&gt;

&lt;p&gt;One of the most powerful features of Insights is its ability to provide application-centric database monitoring and simplify application troubleshooting. To get this added functionality, you need to enable and set up query tagging.  Query tagging augments your SQL statements with information about your application; you could tag queries by business logic, microservice, route, controller, etc. For example, using payment, inventory, business analytics, or shipping tags. You can then find the queries and database  load that the various services create. This may help you find unexpected events, such as spikes for a business analytics tag at certain times of day, or you might see abnormal growth for a payment service trending over the previous week. Sounds great, but how does it work?&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview of Sqlcommenter
&lt;/h2&gt;

&lt;p&gt;Insights uses an open source library called &lt;a href="https://github.com/google/sqlcommenter" rel="noopener noreferrer"&gt;Sqlcommenter&lt;/a&gt;, which allows application information related to your MVC framework to be sent  to the database along with queries as a SQL comment. Sqlcommenter has &lt;a href="https://cloud.google.com/blog/products/databases/sqlcommenter-merges-with-opentelemetry" rel="noopener noreferrer"&gt;recently&lt;/a&gt; merged with &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; to extend the vision of OpenTelemetry to databases and help grow the observability ecosystem. In addition to application information, Sqlcommenter allows OpenTelemetry trace context information to be propagated to the database, making it possible to find correlations between application traces and database query plans. Developers can view this observability information directly in database logs, the information can be integrated into other tools like Cloud SQL  Insights or APM tools such as those from Datadog, Dynatrace, and Splunk. &lt;/p&gt;

&lt;p&gt;Sqlcommenter supports several ORMs and frameworks including Django, SQLAlchemy Ruby on Rails, Knex.js, Sequelize.js, Spring, Hibernate, and more. If you are using one of these supported ORMs, the application tags are automatically created for you, with little to no change to your application code. If you are not using an ORM, you can manually add Sqlcommenter tags to your SQL queries. Let's look at how to enable Sqlcommenter for query tagging in one of the &lt;a href="https://google.github.io/sqlcommenter/#support" rel="noopener noreferrer"&gt;supported languages and frameworks&lt;/a&gt; and how to manually tag queries if you aren't using an ORM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Python and SQLAlchemy with Sqlcommenter
&lt;/h2&gt;

&lt;p&gt;Each supported ORM has documentation and examples to show you how to install and enable it. Here's the process you would follow to set up Sqlcommenter in a Python application that uses &lt;a href="https://github.com/google/sqlcommenter/blob/master/python/sqlcommenter-python/README.md#sqlalchemy" rel="noopener noreferrer"&gt;SQLAlchemy&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Run the following to install Sqlcommenter with the option to record OpenTelemetry trace context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip3 install google-cloud-sqlcommenter[opentelemetry]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then, in your application code, attach the event listener shown below to the before_cursor_execute event of the database engine. This will ensure that all queries that are executed with that engine will have the SQL comments included. Once you've installed Sqlcommenter and added the code below, no further code changes are needed to your application.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import sqlalchemy
from google.cloud.sqlcommenter.sqlalchemy.executor import BeforeExecuteFactory

engine = sqlalchemy.create_engine(...)
listener = BeforeExecuteFactory(
    with_db_driver=True,
    with_db_framework=True,
    with_opentelemetry=True,
)
sqlalchemy.event.listen(engine, 'before_cursor_execute', listener, retval=True)
engine.execute(...) # comment will be added before execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The output in the database logs would look something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2021-08-28 02:33:25.287 PDT [57302] LOG:  statement: SELECT candidate, time_cast FROM
Votes ORDER BY time_cast DESC /*db_driver='pg8000',framework='sqlalchemy%3A1.4.22',controller='index',route='/',
traceparent='00-5bd66ef5095369c7b0d1f8f4bd33716a-c532cb4098ac3dd2-01',
tracestate='congo%%3Dt61rcWkgMzE%%2Crojo%%3D00f067aa0ba902b7'*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;For other ORMs and frameworks, you can find links to the instructions for setting up Sqlcommenter &lt;a href="https://google.github.io/sqlcommenter/#support" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Manually tagging queries using the Sqlcommenter spec
&lt;/h2&gt;

&lt;p&gt;If you're not using one of the supported ORMs, you can add comments to your SQL statements manually by following the Sqlcommenter &lt;a href="https://google.github.io/sqlcommenter/spec/" rel="noopener noreferrer"&gt;specification&lt;/a&gt;. The Sqlcommenter algorithm adds a comment containing serialized key value pairs to a SQL statement. An overview of the algorithm is below; to understand the different pieces of the algorithm  in more depth, see the &lt;a href="https://google.github.io/sqlcommenter/spec/#sql-commenter" rel="noopener noreferrer"&gt;algorithm documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sql_commenter(sql, attributes):
    if contains_sql_comment(sql):
        return sql # DO NOT mutate a statement with an already present comment.

    serialized_key_value_pairs := []

    for each attribute in attributes:
        serialized := serialize_key_value_pair(attribute)
        if serialized:
            serialized_key_value_pairs.append(serialized)

    sorted := sort(serialized_key_value_pairs)
    concatenated := concatenate(sorted)
    final := affix_comment(sql, concatenated)

    return final
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The following keys are supported for use in Cloud SQL Insights: action, controller, framework, route, application, db_driver, &lt;a href="https://www.w3.org/TR/trace-context/#traceparent-field" rel="noopener noreferrer"&gt;traceparent&lt;/a&gt;, and &lt;a href="https://www.w3.org/TR/trace-context/#tracestate-field" rel="noopener noreferrer"&gt;tracestate&lt;/a&gt;. However, if you're not using Cloud SQL Insights, you can include any other custom keys as well.&lt;/p&gt;

&lt;p&gt;The following information passed to the Sqlcommenter algorithm:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sql_commenter('SELECT * FROM POSTS, [
traceparent='00-ff19308b1f17fedc5864e929bed1f44e-6ddace73a9debf63-01',
     route='posts',
     action='index',
     controller='posts',
    application='SqlcommenterBlogDemo'
])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Results in this output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM POSTS/*action='index',application='SqlcommenterBlogDemo',
controller='posts',route='/posts',traceparent='00-ff19308b1f17fedc5864e929bed1f44e-6ddace73a9debf63-01'*/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you're running PostgreSQL for Cloud SQL databases, and you haven't &lt;a href="https://cloud.google.com/sql/docs/postgres/using-query-insights" rel="noopener noreferrer"&gt;checked out Insights&lt;/a&gt; yet, now is the time! If you're using one of the ORMs or frameworks that Sqlcommenter supports, you can find instructions in the documentation for &lt;a href="https://google.github.io/sqlcommenter/#support" rel="noopener noreferrer"&gt;how to enable it for each language and framework&lt;/a&gt;, along with some sample applications in the GitHub repo.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/google" rel="noopener noreferrer"&gt;
        google
      &lt;/a&gt; / &lt;a href="https://github.com/google/sqlcommenter" rel="noopener noreferrer"&gt;
        sqlcommenter
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;sqlcommenter&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;**sqlcommenter is donated to &lt;a href="https://opentelemetry.io/" rel="nofollow noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt;. We will continue to maintain this repository until opentelemetry SQLCommenter feature stabilizes. Further updates will be added here in due time. See the migration plans in &lt;a href="https://github.com/open-telemetry/opentelemetry-java-contrib/issues/205#issuecomment-1018136934" rel="noopener noreferrer"&gt;this comment&lt;/a&gt;. **&lt;/p&gt;

&lt;p&gt;&lt;a href="https://google.github.io/sqlcommenter/" rel="nofollow noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contains all the various &lt;code&gt;sqlcommenter-*&lt;/code&gt; implementations.&lt;/p&gt;

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterpython/sqlcommenter-python/README.md" rel="noopener noreferrer"&gt;Python&lt;/a&gt;
&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterpython/sqlcommenter-python/README.md#django" rel="noopener noreferrer"&gt;django&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterpython/sqlcommenter-python/README.md#psycopg2" rel="noopener noreferrer"&gt;psycopg2&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterpython/sqlcommenter-python/README.md#sqlalchemy" rel="noopener noreferrer"&gt;sqlalchemy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 &lt;a href="https://github.com/google/sqlcommenterjava/sqlcommenter-java/README.md" rel="noopener noreferrer"&gt;Java&lt;/a&gt;

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterjava/sqlcommenter-java/README.md#hibernate" rel="noopener noreferrer"&gt;Hibernate&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterjava/sqlcommenter-java/README.md#spring-hibernate" rel="noopener noreferrer"&gt;Spring+Hibernate&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 Ruby

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterruby/sqlcommenter-ruby/sqlcommenter_rails/README.md" rel="noopener noreferrer"&gt;Rails&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 &lt;a href="https://github.com/google/sqlcommenternodejs/sqlcommenter-nodejs/README.md" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenternodejs/sqlcommenter-nodejs/packages/sqlcommenter-knex/README.md" rel="noopener noreferrer"&gt;Knex.js&lt;/a&gt;
&lt;/li&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenternodejs/sqlcommenter-nodejs/packages/sqlcommenter-sequelize/README.md" rel="noopener noreferrer"&gt;Sequelize.js&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li class="task-list-item"&gt;

 Php

&lt;ul class="contains-task-list"&gt;
&lt;li class="task-list-item"&gt;
 &lt;a href="https://github.com/google/sqlcommenterphp/sqlcommenter-php/packages/sqlcommenter-laravel" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/google/sqlcommenter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;If you have any feedback or questions, you can raise them in the &lt;a href="https://github.com/google/sqlcommenter/issues" rel="noopener noreferrer"&gt;issue tracker&lt;/a&gt; or reach out on &lt;a href="https://twitter.com/jankleinert" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>observability</category>
      <category>sql</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Learning to Read Music with the Web MIDI API</title>
      <dc:creator>Jan Kleinert</dc:creator>
      <pubDate>Thu, 23 Jan 2020 01:38:31 +0000</pubDate>
      <link>https://dev.to/jankleinert/learning-to-read-music-with-the-web-midi-api-d09</link>
      <guid>https://dev.to/jankleinert/learning-to-read-music-with-the-web-midi-api-d09</guid>
      <description>&lt;h2&gt;
  
  
  Learning to Read Music with the Web MIDI API
&lt;/h2&gt;

&lt;p&gt;My two daughters both take piano lessons. Their music school has some nice online learning web apps and tools that help them with music theory and sight reading. There's one web app that they use (pictured below) that displays a note on the staff, and below the staff there is a picture of a keyboard. They have to click the key on the keyboard that corresponds with the note on the staff. It's been a good tool for them to practice recognizing notes quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oxoFUey1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jankleinert.com/assets/images/practicing-piano.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oxoFUey1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jankleinert.com/assets/images/practicing-piano.jpg" alt="child using a piano web app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've noticed that my youngest daughter will sometimes get up from the computer, walk over to our digital piano, play the note there, and then come back and click the key on the screen. That got me thinking - maybe these skills would transfer better if she could play the note directly on our digital piano to match what she sees on the screen. So I set out to build a simple web app that would do just that - display a note on the staff, and while connected to a digital piano or keyboard, use the Web MIDI API to let her know if she played the right note that corresponds with what's on the staff. To build this web app, I had to learn about MIDI and the Web MIDI API. In this article, I'll share what I learned and how the web app was created.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is MIDI?
&lt;/h3&gt;

&lt;p&gt;Before I can talk about the Web MIDI API, it's important to have a basic understanding of what MIDI is. If you were on the internet in the mid 1990s, then the word MIDI may be closely associated in your mind with the sounds emanating on auto-play from Geocities websites. &lt;a href="https://www.cameronsworld.net/"&gt;Something like this&lt;/a&gt; perhaps. MIDI isn't sound or audio though.&lt;/p&gt;

&lt;p&gt;MIDI stands for Musical Instrument Digital Interface, and it's a technical standard that has been around since the 1980s. It's used for communication between digital musical instruments, computers, audio devices, etc. For the purposes of this article and the demo app, the most important aspects of MIDI you need to understand are MIDI messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  MIDI Messages
&lt;/h3&gt;

&lt;p&gt;There are a few types of MIDI messages, but I only ended up using &lt;a href="https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message"&gt;Channel Voice Messages&lt;/a&gt;. There are different events -- or pieces of information -- that can be represented by channel voice messages, such as Note On, Note Off, and Polyphonic Key Pressure. When MIDI messages are sent, they're transmitted on MIDI channels. Up to 16 channels are supported. For the purposes of this demo app, we are only using channel 1. A Note On message gets sent when a key is pressed on the digital piano. This Note On message consists of three pieces of numeric information: the type of event (144 represents a Note On event on channel 1) the note number (from 0 - 127, where middle C is 60), and the velocity (how hard the key was pressed).&lt;/p&gt;

&lt;h5&gt;
  
  
  Note On Message Example
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event Type&lt;/th&gt;
&lt;th&gt;Note Number&lt;/th&gt;
&lt;th&gt;Velocity (how hard the key was pressed)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;144 (Note On)&lt;/td&gt;
&lt;td&gt;0-127&lt;/td&gt;
&lt;td&gt;1-127&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With that understanding of what information is sent when a key is pressed and what it means, the next step is to learn about the Web MIDI API, so that we can make sense of that information in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the Web MIDI API?
&lt;/h3&gt;

&lt;p&gt;The Web MIDI API allows us to interact with MIDI-enabled devices via the browser. This could mean using a MIDI-enabled device as an input to a web application, or it could mean sending MIDI messages from a web application to a MIDI-enabled device. The API is pretty straightforward and easy to get started with.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compatibility
&lt;/h4&gt;

&lt;p&gt;Before moving on, it's important to note that the Web MIDI API currently doesn't have wide browser support. It's currently only supported in Chrome, Opera, and the Android Browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://caniuse.com/#search=web%20midi"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vVtHwXai--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://jankleinert.com/assets/images/caniuse.png" alt="caniuse.com screenshot for web midi"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to use the features of the Web MIDI API for more than just experimentation, and you need broad browser support, check out JZZ, a MIDI library for Node.js and web browsers. I haven't used it myself, but it was often referenced as a good alternative when I was reading about the Web MIDI API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Demo App
&lt;/h3&gt;

&lt;p&gt;I'll walk you through some of the key parts of the code, but you can find the full source code for the demo app here: &lt;a href="https://github.com/jankleinert/get-your-notes-on"&gt;https://github.com/jankleinert/get-your-notes-on&lt;/a&gt;. A live version of the app is hosted &lt;a href="https://noteon-demo-getyournoteson.b9ad.pro-us-east-1.openshiftapps.com/"&gt;here&lt;/a&gt; - if you've got a MIDI device, hook it up to your computer and try it out! Here's a screen capture of the web app in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--soaJsR6z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://jankleinert.com/assets/images/blog/web-midi-api-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--soaJsR6z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://jankleinert.com/assets/images/blog/web-midi-api-demo.gif" alt="animated gif screen capture of the app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Checking for Browser Support of the Web MIDI API
&lt;/h4&gt;

&lt;p&gt;The first step is to check if the browser supports the Web MIDI API. As shown in the code snippet below, we'll check &lt;code&gt;navigator.requestMIDIAccess&lt;/code&gt;, and if that is &lt;code&gt;true&lt;/code&gt;, then we'll call &lt;code&gt;navigator.requestMIDIAccess()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (navigator.requestMIDIAccess) {
  console.log('WebMIDI is supported in this browser.');
  navigator.requestMIDIAccess().then(onMIDISuccess, onMIDIFailure);
} else {
  console.log('WebMIDI is not supported in this browser.');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  onMIDISuccess Callback
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;onMIDISuccess()&lt;/code&gt; is the function that is called if we successfully are able to get MIDI access. In that function, we do a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Shuffle the array of notes (&lt;code&gt;level1Notes&lt;/code&gt;), so that the user doesn't see the notes in the same order each time they use the app.&lt;/li&gt;
&lt;li&gt;  Draw the first note in the array on the staff.&lt;/li&gt;
&lt;li&gt;  Get any available MIDI inputs (there will typically only be one). When an &lt;code&gt;onmidimessage&lt;/code&gt; event fires, we'll call &lt;code&gt;getMIDIMessage&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function onMIDISuccess(midiAccess) {
  shuffleArray(level1Notes);
  drawNote(level1Notes[noteIndex]);

  var inputs = midiAccess.inputs;
  var outputs = midiAccess.outputs;

  for (var input of midiAccess.inputs.values()) {
    input.onmidimessage = getMIDIMessage;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 60 represents middle C                           
var level1Notes = [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Listen for noteOn
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;getMIDIMessage()&lt;/code&gt; function, we look at the three pieces of information that are passed as part of the channel voice message. As we learned earlier, a &lt;code&gt;noteOn&lt;/code&gt; message on MIDI channel 1 has a value of &lt;code&gt;144&lt;/code&gt;. So that is what we're looking for in the switch statement. The other two pieces of information represent the note number and velocity. We update some text on the web app to show these three pieces of information, and then we call &lt;code&gt;noteOnListener&lt;/code&gt; and pass in the note number.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getMIDIMessage(message) {
  var command = message.data[0];
  var note = message.data[1];
  var velocity = message.data[2];

  switch (command) {
    case 144: // noteOn
      document.querySelector('.note-info').textContent = 'Command: ' + command +
        ' , Note: ' + note + ' , Velocity: ' + velocity;
      noteOnListener(note);
      break;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In a more complex application, you could do a lot more in this function. You could listen for noteOff messages as well, for example.&lt;/p&gt;
&lt;h4&gt;
  
  
  noteOnListener
&lt;/h4&gt;

&lt;p&gt;In the &lt;code&gt;noteOnListener()&lt;/code&gt; function, we do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Check if the note that was played was the correct note. Set the color of the note to green or red depending on the outcome.&lt;/li&gt;
&lt;li&gt;  After 1.5 seconds, reset the staff and display the next note in the array.&lt;/li&gt;
&lt;li&gt;  Display the score once all the notes have been played.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's pretty much all there is to it! If you want to look at the code, you'll find it here:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vJ70wriM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/github-logo-ba8488d21cd8ee1fee097b8410db9deaa41d0ca30b004c0c63de0a479114156f.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jankleinert"&gt;
        jankleinert
      &lt;/a&gt; / &lt;a href="https://github.com/jankleinert/get-your-notes-on"&gt;
        get-your-notes-on
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple Web MIDI API demo. Connect a digital keyboard and play the notes on the screen
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;PRs welcome, especially if you'd like to add support for bass clef notes! :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Want to Learn More?
&lt;/h3&gt;

&lt;p&gt;Here's a list of resources and links where you can learn more about the Web MIDI API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://bit.ly/web-midi-api"&gt;Slides from my talk on this topic at Node+JS Interactive 2019&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.midi.org/developer-web-midi-info"&gt;Web MIDI API info from midi.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.smashingmagazine.com/2018/03/web-midi-api/"&gt;An excellent article on Getting Started with the Web MIDI API&lt;/a&gt;. I learned a lot from this one!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>midi</category>
      <category>music</category>
    </item>
    <item>
      <title>Node.js Session Management Using Express Sessions, Redis, and Passport - Part 2</title>
      <dc:creator>Jan Kleinert</dc:creator>
      <pubDate>Fri, 19 Jul 2019 15:37:14 +0000</pubDate>
      <link>https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-2-4132</link>
      <guid>https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-2-4132</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja"&gt;Part 1&lt;/a&gt; of this tutorial, we went step-by-step through the process of building a web app with Node.js and Express that uses &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt; and &lt;a href="https://github.com/tj/connect-redis" rel="noopener noreferrer"&gt;connect-redis&lt;/a&gt; as a way of helping users understand how session management works.&lt;/p&gt;

&lt;p&gt;In this second part, we will expand on the previous app by implementing authentication using &lt;a href="http://www.passportjs.org/" rel="noopener noreferrer"&gt;Passport&lt;/a&gt; and exploring how authentication and sessions work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;p&gt;If you followed the steps in &lt;a href="https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja"&gt;Part 1&lt;/a&gt;, then you can move on to the next section. If not, here's what you need to do.&lt;/p&gt;

&lt;p&gt;Clone this GitHub repo that has the code for the demo app. The master branch contains the code as it is at the end of Part 1. You'll also need to install Redis and start the Redis server if you don't already have it installed. If you need to install Redis, you can take a look at &lt;a href="https://redis.io/topics/quickstart" rel="noopener noreferrer"&gt;this documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/jankleinert/redis-session-demo
$ cd redis-session-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try running the app to make sure it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install
$ export SESSION_SECRET=some_secret_value_here
$ npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser, and you should see something like this.&lt;/p&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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-session-id-first-load.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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-session-id-first-load.png" alt="demo app screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Set up a MySQL user database
&lt;/h3&gt;

&lt;p&gt;Regardless of whether or not you completed Part 1, you'll need to ensure you have MySQL installed. &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/installing.html" rel="noopener noreferrer"&gt;Instructions are here&lt;/a&gt; if you need to install and set up MySQL. Next, launch &lt;code&gt;mysql&lt;/code&gt; and create a new database and a new table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; CREATE DATABASE redis_session_demo; 
mysql&amp;gt; USE redis_session_demo;
mysql&amp;gt; CREATE TABLE users (id varchar(20), email varchar(20), password varchar(60));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will be our user database. To speed things up, rather than having an account creation page, we'll manually insert a test user into the database. The app will be using &lt;code&gt;bcrypt&lt;/code&gt; to create a hash for our passwords. Our test user will have &lt;code&gt;id = a1b2c3d4&lt;/code&gt;, &lt;code&gt;email = test@example.com&lt;/code&gt;, and &lt;code&gt;password = password&lt;/code&gt;. You can use &lt;a href="https://bcrypt-generator.com/" rel="noopener noreferrer"&gt;this site&lt;/a&gt; to create a hashed password. Next, we'll insert that into our user database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; INSERT INTO users (id, email, password) VALUES ('a1b2c3e4', 'test@example.com', '$2y$12$7Mj1fG3bdlpmRcXtZpwimOI4pItCQcj5x2.ZqydPbR5wWlKGVaQVe');               
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick recap of the demo app
&lt;/h3&gt;

&lt;p&gt;The demo app was built using &lt;a href="https://expressjs.com/en/starter/generator.html" rel="noopener noreferrer"&gt;express-generator&lt;/a&gt; to create the app skeleton. It's using &lt;a href="https://pugjs.org/api/getting-started.html" rel="noopener noreferrer"&gt;Pug&lt;/a&gt; for the view engine. When you click the Pour Another button, it makes a request to an &lt;a href="https://www.craftbeernamegenerator.com/about.html" rel="noopener noreferrer"&gt;API&lt;/a&gt; that will return a machine-learning-generated craft beer name.&lt;/p&gt;

&lt;p&gt;In Part 1, we added a session information panel that displays the session ID, how many more seconds are left before the session expires, and also our session data: the number of beer names that have been viewed in the current session. To implement session management, we used &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt; for the session middleware and &lt;a href="https://github.com/tj/connect-redis" rel="noopener noreferrer"&gt;connect-redis&lt;/a&gt; as the session store. In the next step, we will add links to log in and log out, create a login page, and refactor the session panel that was originally included directly in &lt;code&gt;/views/index.pug&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add authentication support to the frontend
&lt;/h3&gt;

&lt;p&gt;We are going to start by refactoring the session info panel. By moving it to a separate file, it will be easier to include it in multiple pages. Create a new file &lt;code&gt;/views/session.pug&lt;/code&gt; and paste in this code. There is a section at the bottom now that displays whether or not the user is authenticated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    .session
      p Session Info
      if sessionID
        p= 'Session ID: ' + sessionID 
      if sessionExpireTime
        p= 'Session expires in ' + Math.round(sessionExpireTime/1000) + ' seconds'
      if beersViewed
        p= 'Beers viewed in this session: ' + beersViewed
      else 
        p= 'No beers viewed yet in this session.'
      if isAuthenticated
        p= 'Logged in as: ' + email
      else  
        p= 'Not logged in'                      
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, open up &lt;code&gt;/views/index.pug&lt;/code&gt; and replace the &lt;code&gt;.session&lt;/code&gt; section with the following line. It should be lined up in the same column as the &lt;code&gt;h1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    include session.pug              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's time to create the login page. Create &lt;code&gt;/views/login.pug&lt;/code&gt; and paste in this code. It's a simple form with fields for email and password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    extends layout

    block content
      h1= 'Log In'
      .lead
        form#login-form(action='/login', method='post')
          .form-group
            input(name='email', type='text', placeholder='Email', required='')
          .form-group
            input(name='password', type='password', placeholder='Password', required='')
          button.btn.btn-primary(type='submit')= 'Log In'
        if error
          p= error

      include session.pug              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to add Home, Log In, and Log Out links to the navigation. Open &lt;code&gt;layout.pug&lt;/code&gt; and add this code directly below &lt;code&gt;h3.masthead-brand Craft Beer Name Demo&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    nav.nav.nav-masthead.justify-content-center
      a.nav-link(href='/') Home
      a.nav-link(href='/login') Log In
      a.nav-link(href='/logout') Log Out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update app.js
&lt;/h3&gt;

&lt;p&gt;To support adding authentication to the app, we need to install some additional packages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save bcryptjs mysql passport passport-local                       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bcryptjs&lt;/code&gt; is used for for hashing and checking passwords. &lt;a href="http://www.passportjs.org/" rel="noopener noreferrer"&gt;&lt;code&gt;passport&lt;/code&gt;&lt;/a&gt; is the authentication middleware we are using, and &lt;a href="http://www.passportjs.org/packages/passport-local/" rel="noopener noreferrer"&gt;&lt;code&gt;passport-local&lt;/code&gt;&lt;/a&gt; is the authentication strategy, meaning we are authenticating with a username and password.&lt;/p&gt;

&lt;p&gt;Next, open up &lt;code&gt;app.js&lt;/code&gt; and add the following code below the existing &lt;code&gt;require&lt;/code&gt;s.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const loginRouter = require('./routes/login');
const logoutRouter = require('./routes/logout');
const mysql = require('mysql');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');

const mysqlConnection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '', // demo purposes only
  database: 'redis_session_demo'
});

mysqlConnection.connect(function(err) {
  if (err) {
    console.log('error connecting to mysql: ' + err.stack);
    return;
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're using a MySQL database with &lt;code&gt;root&lt;/code&gt; and &lt;code&gt;''&lt;/code&gt; as the login and password. This is only for demo purposes; don't do that in production! You probably also noticed &lt;code&gt;loginRouter&lt;/code&gt; and &lt;code&gt;logoutRouter&lt;/code&gt; reference files that don't exist. We'll create those in the next section.&lt;/p&gt;

&lt;p&gt;Scroll down a bit until you see &lt;code&gt;const redisClient = redis.createClient();&lt;/code&gt;. Directly after that line, add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// configure passport.js to use the local strategy
passport.use(new LocalStrategy(
  { usernameField: 'email' },
  (email, password, done) =&amp;gt; {
    mysqlConnection.query('SELECT * FROM users WHERE email = ?', [email], function (error, results, fields) {
      if (error) throw error;
      var user = results[0];
      if (!user) {
        return done(null, false, { message: 'Invalid credentials.\n' });
      }
      if (!bcrypt.compareSync(password, user.password)) {
        return done(null, false, { message: 'Invalid credentials.\n' });
      }
      return done(null, user);
    });
  }
));

passport.serializeUser((user, done) =&amp;gt; {
  done(null, user.id);
});

passport.deserializeUser((id, done) =&amp;gt; {
  mysqlConnection.query('SELECT * FROM users WHERE id = ?', [id], function (error, results, fields) {
    if (error) {
      done(error, false);
    }
    done(null, results[0]);  
  });
});              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found &lt;a href="https://medium.com/@evangow/server-authentication-basics-express-sessions-passport-and-curl-359b7456003d" rel="noopener noreferrer"&gt;this article&lt;/a&gt; very helpful in understanding what happens during the authentication process. You'll notice that I modeled some parts of this code after what was done in that article.&lt;/p&gt;

&lt;p&gt;Scroll down a bit more in the file until you find &lt;code&gt;app.use('/', indexRouter);&lt;/code&gt;. Replace that line with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.use(passport.initialize());
app.use(passport.session());

app.use('/', indexRouter);
app.use('/login', loginRouter);
app.use('/logout', logoutRouter);                       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is setting up our app to use passport as middleware, and then we're adding the two new routes for logging in and logging out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update routes
&lt;/h3&gt;

&lt;p&gt;The last step we need to take is to update &lt;code&gt;/routes/index.js&lt;/code&gt; and create two new files: &lt;code&gt;/routes/login.js&lt;/code&gt; and &lt;code&gt;/routes/logout.js&lt;/code&gt;. Open &lt;code&gt;/routes/index.js&lt;/code&gt;. In each of the four &lt;code&gt;res.render()&lt;/code&gt; calls, add this to the end of the list of properties in the locals object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null)              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, for example, the &lt;code&gt;res.render()&lt;/code&gt; call in &lt;code&gt;router.get()&lt;/code&gt; would become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null) });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next create &lt;code&gt;/routes/login.js&lt;/code&gt; and paste in this code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const router = express.Router();
const passport = require('passport');

/* GET request for login page */
router.get('/', function(req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();
  res.render('login', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null) });
});


router.post('/', function (req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();
  passport.authenticate('local', (err, user, info) =&amp;gt; {
    if(info) {return res.send(info.message)}
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.login(user, (err) =&amp;gt; {
      if (err) { return next(err); }
      res.render('login', {sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, username: req.user.id, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null)});
    })
  })(req, res, next);
})  

router.get('')

module.exports = router;              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;router.post()&lt;/code&gt; is where our login form submissions are handled using passport, using the local strategy.&lt;/p&gt;

&lt;p&gt;Finally create &lt;code&gt;/routes/logout.js&lt;/code&gt; and paste in this code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require('express');
const router = express.Router();

/* GET request for logout page */
router.get('/', function(req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();   
  req.logout();
  req.session.destroy(function() {
    res.redirect('/');
  });
});

router.get('')

module.exports = router;              
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no page that is displayed specifically for logging out. Instead, when the &lt;code&gt;GET&lt;/code&gt; request is made to &lt;code&gt;/logout&lt;/code&gt;, the app will log the user out, destroy the session, and then redirect them to the home page. At this point there will not be an authenticated user, and a new session will be created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it!
&lt;/h3&gt;

&lt;p&gt;Let's try it out! Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser. When it first loads, you should see the info panel displays a session ID and a time until the session expires, as well as "Not logged in".&lt;/p&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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-add-auth.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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-add-auth.png" alt="add auth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Log In" link in the header and authenticate using our test credentials: &lt;code&gt;test@example.com&lt;/code&gt; / &lt;code&gt;password&lt;/code&gt;. When the page reloads, you should see that you are now logged in as &lt;a href="mailto:test@example.com"&gt;test@example.com&lt;/a&gt;&lt;/p&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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-user-logged-in.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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-user-logged-in.png" alt="user logged in"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you take other actions on the site, you'll see that you stay logged in, but if you click "Log Out" in the navigation, you will no longer be logged in and a new session will be started.&lt;/p&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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-logged-out.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%2Fjankleinert.com%2Fassets%2Fimages%2Fblog%2Fnode-redis-logged-out.png" alt="user logged out"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! You now have a simple app that handles session management as well as authentication. Is it complete? Definitely not! There are lots of improvements and additions that could be made, including an account creation page, better error handling, etc. You can find the complete code for Part 2 &lt;a href="https://github.com/jankleinert/redis-session-demo/tree/auth" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>express</category>
      <category>session</category>
      <category>passport</category>
    </item>
    <item>
      <title>Node.js Session Management Using Express Sessions, Redis, and Passport - Part 1</title>
      <dc:creator>Jan Kleinert</dc:creator>
      <pubDate>Fri, 19 Jul 2019 15:22:05 +0000</pubDate>
      <link>https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja</link>
      <guid>https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja</guid>
      <description>&lt;p&gt;Recently, I set out to create a demo application for my talk at &lt;a href="https://events.redislabs.com/redis-day-nyc/redis-day-nyc-2019/" rel="noopener noreferrer"&gt;Redis Day NYC&lt;/a&gt; that illustrates how session management works in a Node.js/Express web app, using Redis as the session store and then adds authentication on top of all that. Understanding the concepts and how they work together is one thing, but I hadn't actually built an app that used all these components together before.&lt;/p&gt;

&lt;p&gt;As part of my initial research, I looked for existing tutorials or examples that did what I was trying to do. I found several good &lt;a href="https://codeforgeek.com/manage-session-using-node-js-express-4/" rel="noopener noreferrer"&gt;blog&lt;/a&gt; &lt;a href="https://medium.com/mtholla/managing-node-js-express-sessions-with-redis-94cd099d6f2f" rel="noopener noreferrer"&gt;posts&lt;/a&gt; &lt;a href="https://blog.risingstack.com/node-hero-node-js-authentication-passport-js/" rel="noopener noreferrer"&gt;and&lt;/a&gt; &lt;a href="https://medium.com/@evangow/server-authentication-basics-express-sessions-passport-and-curl-359b7456003d" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt;, but none did exactly what I was looking for. Part 1 of this tutorial will take you step-by-step through the process of building a web app with Node.js and Express that uses &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt; and &lt;a href="https://github.com/tj/connect-redis" rel="noopener noreferrer"&gt;connect-redis&lt;/a&gt; as a way of helping users understand how session management works. Part 2 will expand on this by implementing authentication using &lt;a href="http://www.passportjs.org/" rel="noopener noreferrer"&gt;Passport&lt;/a&gt; and exploring how authentication and sessions work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the code for the craft beer name demo app
&lt;/h3&gt;

&lt;p&gt;We will be starting with a simple demo app, and once we have that up and running, we'll add in session management and then authentication. Let's start by cloning the GitHub repo that has the code for the demo app and then switch to the beer-demo branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git clone https://github.com/jankleinert/redis-session-demo
$ cd redis-session-demo
$ git checkout beer-demo 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's try running the app to make sure it works.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install
$ npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser, and you should see something like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6gf3f3flka8e31crry7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6gf3f3flka8e31crry7.png" alt="demo app screenshot" width="500" height="480"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Understanding the demo app
&lt;/h3&gt;

&lt;p&gt;The demo app was built using &lt;a href="https://expressjs.com/en/starter/generator.html" rel="noopener noreferrer"&gt;express-generator&lt;/a&gt; to create the app skeleton. It's using &lt;a href="https://pugjs.org/api/getting-started.html" rel="noopener noreferrer"&gt;Pug&lt;/a&gt; for the view engine. When you click the Pour Another button, it makes a request to an &lt;a href="https://www.craftbeernamegenerator.com/about.html" rel="noopener noreferrer"&gt;API&lt;/a&gt; that will return a machine-learning-generated craft beer name. That's really all the app does at this point.&lt;/p&gt;

&lt;p&gt;The three main files we'll be working with are &lt;code&gt;app.js&lt;/code&gt;, &lt;code&gt;/routes/index.js&lt;/code&gt;, and &lt;code&gt;/views/index.pug&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6pjxyff4f26n2ds6mdc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6pjxyff4f26n2ds6mdc.png" alt="dir structure" width="400" height="890"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why do we care about session management anyway?
&lt;/h3&gt;

&lt;p&gt;"Session" is such an overloaded term, and can mean very different things depending on context. In this tutorial, we're talking about a user’s session in a web application. You can think of it as the set of requests and responses within a web app, initiated by a single user, from the start of their interaction until they end the session or it expires.&lt;/p&gt;

&lt;p&gt;Why do we care about or need a construct like a session? HTTP is stateless, so each request and response pair is independent of the others. By default, no state is maintained and the server doesn’t know who you are from one request to another. Session management gives us the ability to assign an identifier to a user session, and use that ID to store state or data relevant to the session. This could be something like whether or not a user is authenticated, the items in a shopping cart, and so on - whatever state needs to be kept during that session.&lt;/p&gt;

&lt;p&gt;There are multiple ways of handling session management, but we’re going to look at one specific way, where session data is kept in a session store, and we’ll be using Redis as the session store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nxcz96h619vj7f9hri8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0nxcz96h619vj7f9hri8.png" alt="client and server" width="600" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the client side, a cookie is stored with the session ID but none of the session data. In your application’s session store (Redis in this case), the session ID is also stored, along with the session data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add a session info panel to the app
&lt;/h3&gt;

&lt;p&gt;To make it easy to visualize what's happening with a session, we will add a session info panel to the app. Open &lt;code&gt;/views/index.pug&lt;/code&gt; and add the following code to the bottom of the file. Be careful with your indentation; &lt;code&gt;.session&lt;/code&gt; should line up in the same column as &lt;code&gt;h1&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    .session
      p Session Info
      if sessionID
        p= 'Session ID: ' + sessionID 
      if sessionExpireTime
        p= 'Session expires in ' + Math.round(sessionExpireTime) + ' seconds'
      if beersViewed
        p= 'Beers viewed in this session: ' + beersViewed                           
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This panel will display the session ID, how many more seconds are left before the session expires, and also our session data: the number of beer names that have been viewed in this session. We'll be specifying those values in &lt;code&gt;/routes/index.js&lt;/code&gt; in a later step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add express-session and connect-redis to app.js
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;express-session&lt;/code&gt; is session middleware for Express. It's pretty straightforward to set up and use. There are quite a few &lt;a href="https://github.com/expressjs/session#compatible-session-stores" rel="noopener noreferrer"&gt;compatible session stores&lt;/a&gt; that you can use for storing session data. We will be using &lt;a href="https://www.npmjs.com/package/connect-redis" rel="noopener noreferrer"&gt;&lt;code&gt;connect-redis&lt;/code&gt;&lt;/a&gt;. Let's start by installing the npm modules that we need.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm install --save express-session uuid redis connect-redis                       
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, open up &lt;code&gt;app.js&lt;/code&gt; and add the following code below the existing &lt;code&gt;require&lt;/code&gt;s. &lt;code&gt;uuid&lt;/code&gt; will be used to generate a unique ID to use for our session ID.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const uuid = require('uuid/v4')
const session = require('express-session');
const redis = require('redis');
const redisStore = require('connect-redis')(session);   

const redisClient = redis.createClient();

redisClient.on('error', (err) =&amp;gt; {
  console.log('Redis error: ', err);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Before we move on, make sure you have Redis installed and that the Redis server is running. If you need to install Redis, you can take a look at &lt;a href="https://redis.io/topics/quickstart" rel="noopener noreferrer"&gt;this documentation&lt;/a&gt;. Now we can set up the session middleware and tell it to use our Redis store as the session store. Add this code above the line &lt;code&gt;app.use('/', indexRouter);&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.use(session({
  genid: (req) =&amp;gt; {
    return uuid()
  },
  store: new redisStore({ host: 'localhost', port: 6379, client: redisClient }),
  name: '_redisDemo', 
  secret: process.env.SESSION_SECRET,
  resave: false,
  cookie: { secure: false, maxAge: 60000 }, // Set to secure:false and expire in 1 minute for demo purposes
  saveUninitialized: true
}));                            
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;There are a couple things to note about this code. The cookie that stores the session ID will be named "_redisDemo". We are using an environment variable to set the secret. In the next step, we'll export that env variable (you can set it to whatever you like). We are setting the session expiration to 1 minute to make it easier to understand what's happening in the demo app. In a real application, you'd set the maxAge to something more reasonable for your application. In your terminal, stop &lt;code&gt;nodemon&lt;/code&gt; and then run the following.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ export SESSION_SECRET=some_secret_value_here &amp;amp;&amp;amp; npm run dev                   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Add session management code to /routes/index.js
&lt;/h3&gt;

&lt;p&gt;The last step will be to add logic to keep track of the number of beer names viewed per session and to pass the session-related information through to the session panel. Open &lt;code&gt;/routes/index.js&lt;/code&gt; and replace the existing &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;post&lt;/code&gt; with the code below.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.get('/', function(req, res, next) {
  var expireTime = req.session.cookie.maxAge / 1000; 
  res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: null });
});

router.post('/', function (req, res) {
  request('https://www.craftbeernamegenerator.com/api/api.php?type=trained', function (err, response, body) {
    if (req.session.views) {
      req.session.views++
    } else {
      req.session.views = 1
    }
    var expireTime = req.session.cookie.maxAge / 1000;   

    if(err){
      res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
    } else {
      var beerInfo = JSON.parse(body)

      if(beerInfo.status != 200){
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
      } else {
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: beerInfo.data.name, beerStyle: beerInfo.data.style, error: null});
      }
    }
  });
});                         
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What did we change? In &lt;code&gt;router.get&lt;/code&gt;, we added &lt;code&gt;expireTime&lt;/code&gt; so that we can calculate the amount of time until the session expires. Then in &lt;code&gt;res.render&lt;/code&gt;, we are passing some additional values: the session ID from &lt;code&gt;req.sessionID&lt;/code&gt;, the expire time we just calculated, and the number of beers viewed per session, which is stored as &lt;code&gt;req.session.views&lt;/code&gt;. On the first page view of a session, there will not be a value for &lt;code&gt;req.session.views&lt;/code&gt;, but our template knows how to handle that.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;router.post&lt;/code&gt;, after we make the API request for the beer name, we are either incrementing &lt;code&gt;req.session.views&lt;/code&gt; or setting it to &lt;code&gt;1&lt;/code&gt; if this is the first beer name viewed in the session. Then, similar to what we saw above, we're passing along the additional session-related information in &lt;code&gt;res.render&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Session management in action!
&lt;/h3&gt;

&lt;p&gt;With everything in place now, open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; in your browser. When it first loads, you should see the info panel displays a session ID and a time until the session expires.&lt;/p&gt;

&lt;p&gt;Click on the Pour Another button (within 60 seconds, so your session doesn't expire), and you should see that the session ID remains the same, and now you also see the number of beers viewed in the session set to &lt;code&gt;1&lt;/code&gt;. If you open dev tools in your browser and view cookies, you should see a cookie named &lt;code&gt;_redisDemo&lt;/code&gt;, and part of its value will contain the session ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo69khfy56qsllnllisp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo69khfy56qsllnllisp.png" alt="session with cookie" width="648" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, if you start &lt;code&gt;redis-cli&lt;/code&gt; and then issue the following command, where &lt;code&gt;YOUR_SESSION_ID&lt;/code&gt; is replaced with the session ID shown in your browser, you should see the session data that's being stored in Redis for that session, including the views.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ redis-cli
$ get "sess:YOUR_SESSION_ID"                            
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The output should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg256neuycy5rzhz6lhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg256neuycy5rzhz6lhb.png" alt="redis-cli" width="744" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Play around with the app some more to get a better understanding for how the sessions work. What happens if you close and then quickly re-open your browser? What happens if you wait more than 60 seconds and then refresh the page?&lt;/p&gt;

&lt;p&gt;At this point, hopefully you have a better understanding of what session management is and how to implement it for a Node.js app using &lt;code&gt;express-session&lt;/code&gt; and &lt;code&gt;connect-redis&lt;/code&gt;. In Part 2, we'll build on what we've done in this tutorial by adding authentication to the app using Passport.&lt;/p&gt;

&lt;p&gt;Just want the code from Part 1? Get it here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jankleinert" rel="noopener noreferrer"&gt;
        jankleinert
      &lt;/a&gt; / &lt;a href="https://github.com/jankleinert/redis-session-demo" rel="noopener noreferrer"&gt;
        redis-session-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Demo app that shows session management for a Node.js app using express-sessions and connect-redis
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;redis-session-demo overview&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Demo app that shows session management for a Node.js app using express-sessions and connect-redis. Originally created for Redis Day NYC 2019: &lt;a href="https://events.redislabs.com/sessions/life-user-session/" rel="nofollow noopener noreferrer"&gt;https://events.redislabs.com/sessions/life-user-session/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app queries an API for ML-generated craft beer names and displays them on the page. There is a session management panel that displays session ID, time until the session expires, and the number of beer names viewed in that session.&lt;/p&gt;

&lt;p&gt;Learn more about how it works in this &lt;a href="https://jankleinert.com/blog/2019/07/11/nodejs-session-management-using-express-sessions-and-redis-part-1.html" rel="nofollow noopener noreferrer"&gt;tutorial:&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;how to run&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Make sure you have have Redis server running locally:&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;redis-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then clone this repo, run npm install and then run it in dev mode:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;git clone https://github.com/jankleinert/redis-session-demo
cd redis-session-demo
npm install
export SESSION_SECRET=&amp;lt;some value you choose&amp;gt;
npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then in your browser, go to &lt;a href="http://localhost:3000" rel="nofollow noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. It should look something like this:&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/jankleinert/redis-session-demoscreenshot.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cdHXg6Es--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://github.com/jankleinert/redis-session-demoscreenshot.png" alt="screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;how it works&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;This demo uses &lt;a href="https://github.com/expressjs/session" rel="noopener noreferrer"&gt;express-session&lt;/a&gt; for session management and &lt;a href="https://github.com/tj/connect-redis" rel="noopener noreferrer"&gt;connect-redis&lt;/a&gt; as the session store.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;branches&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;master&lt;/em&gt; branch…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jankleinert/redis-session-demo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>node</category>
      <category>express</category>
      <category>redis</category>
      <category>session</category>
    </item>
  </channel>
</rss>
