Giter Club home page Giter Club logo

nestjs-clickhouse's Introduction

Description

ClickHouse® is an open-source, high performance columnar OLAP database management system for real-time analytics using SQL. ClickHouse combined with TypeScript helps you develop better type safety with your ClickHouse queries, giving you end-to-end typing.

Installation

Install the following package:

$ npm i --save @depyronick/nestjs-clickhouse

Quick Start

This NestJS module is a wrapper for @depyronick/clickhouse-client. You can find latest documentation for methods there.

Importing the module

Once the installation process is complete, we can import the ClickHouseModule into the root AppModule.

import { Module } from '@nestjs/common';
import { ClickHouseModule } from '@depyronick/nestjs-clickhouse';

@Module({
  imports: [
    ClickHouseModule.register([
      {
        name: 'ANALYTICS_SERVER',
        host: '127.0.0.1',
        password: '7h3ul71m473p4555w0rd',
      },
    ]),
  ],
})
export class AppModule {}

The register() method will register a ClickHouse client with the specified connection options.

See ClickHouseOptions object for more information.

Each registered client should have an unique name definition. The default value for name property is CLICKHOUSE_DEFAULT. This property will be used as an injection token.

Interacting with ClickHouse Client

To interact with the ClickHouse server that you have just registered, inject it to your class using the injection token.

constructor(
	@Inject('ANALYTICS_SERVER')
	private analyticsServer: ClickHouseClient
) {}

The ClickHouseClient class is imported from the @depyronick/nestjs-clickhouse.

Examples

⚠️ These are only a few examples, please see @depyronick/clickhouse-client for up to date methods, like Parametrized Query Capabilities.

ClickHouseClient.query<T>(query: string): Observable<T>

import { Inject, Injectable } from '@nestjs/common';
import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';

interface VisitsTable {
  timestamp: number;
  ip: string;
  userAgent: string;
  os: string;
  version: string;
  // ...
}

@Injectable()
export class AppService {
  constructor(
    @Inject('ANALYTICS_SERVER')
    private readonly analyticsServer: ClickHouseClient,
  ) {
    this.analyticsServer
      .query<VisitsTable>('SELECT * FROM visits LIMIT 10')
      .subscribe({
        error: (err: any): void => {
          // called when an error occurred during query
        },
        next: (row): void => {
          // called for each row
          // the type of row property here is VisitsTable
        },
        complete: (): void => {
          // called when stream is completed
        },
      });
  }
}

ClickHouseClient.queryPromise<T>(query: string): Promise<T[]>

import { Inject, Injectable } from '@nestjs/common';
import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';

interface VisitsTable {
  timestamp: number;
  ip: string;
  userAgent: string;
  os: string;
  version: string;
  // ...
}

@Injectable()
export class AppService {
  constructor(
    @Inject('ANALYTICS_SERVER')
    private readonly analyticsServer: ClickHouseClient,
  ) {
    this.analyticsServer
      .queryPromise<VisitsTable>('SELECT * FROM visits LIMIT 10')
      .then((rows: VisitsTable[]) => {
        // all retrieved rows
      })
      .catch((err) => {
        // called when an error occurred during query
      });

    // or

    const rows = await this.analyticsServer.queryPromise(
      'SELECT * FROM visits LIMIT 10',
    );
  }
}

ClickHouseClient.insert<T>(table: string, data: T[]): Observable<any>

The insert method accepts two inputs.

  • table is the name of the table that you'll be inserting data to.
    • Table value could be prefixed with database like analytics_db.visits.
  • data: T[] array of JSON objects to insert.
import { Inject, Injectable } from '@nestjs/common';
import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';

interface VisitsTable {
  timestamp: number;
  ip: string;
  userAgent: string;
  os: string;
  version: string;
  // ...
}

@Injectable()
export class AppService {
  constructor(
    @Inject('ANALYTICS_SERVER')
    private readonly analyticsServer: ClickHouseClient,
  ) {
    this.analyticsServer
      .insert<VisitsTable>('visits', [
        {
          timestamp: new Date().getTime(),
          ip: '127.0.0.1',
          os: 'OSX',
          userAgent:
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/95.0.4638.69 Safari/537.36',
          version: '1.0.0',
        },
      ])
      .subscribe({
        error: (err: any): void => {
          // called when an error occurred during insert
        },
        next: (): void => {
          // currently next does not emits anything for inserts
        },
        complete: (): void => {
          // called when insert is completed
        },
      });
  }
}

Multiple Clients

You can register multiple clients in the same application as follows:

@Module({
  imports: [
    ClickHouseModule.register([
      {
        name: 'ANALYTICS_SERVER',
        host: '127.0.0.1',
        password: '7h3ul71m473p4555w0rd',
      },
      {
        name: 'CHAT_SERVER',
        host: '192.168.1.110',
        password: 'ch5ts3rv3Rp455w0rd',
      },
    ]),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Then you can interact with these servers using their assigned injection tokens.

constructor(
    @Inject('ANALYTICS_SERVER')
    private analyticsServer: ClickHouseClient,

    @Inject('CHAT_SERVER')
    private chatServer: ClickHouseClient
) { }

Async Registration & Async Providers

For example, if you want to wait for the application to accept new connections until the necessary configuration settings for clickhouse are received from an asynchronous target, you can use the registerAsync method.

import { Inject, Module } from '@nestjs/common';
import { ClickHouseModule } from '@depyronick/nestjs-clickhouse';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: ['.development.env'],
      isGlobal: true,
    }),
    ClickHouseModule.registerAsync({
      useFactory: (config: ConfigService) => {
        return {
          host: config.get('CH_HOST'),
          database: config.get('CH_DB'),
          password: config.get('CH_PWD'),
          username: config.get('CH_USERNAME'),
        };
      },
      inject: [ConfigService],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

Then you can use the CLICKHOUSE_ASYNC_INSTANCE_TOKEN to inject the ClickHouseClient with the asynchronous configuration that you just provided.

import {
  ClickHouseClient,
  CLICKHOUSE_ASYNC_INSTANCE_TOKEN,
} from '@depyronick/nestjs-clickhouse';

export class AppModule {
  constructor(
    @Inject(CLICKHOUSE_ASYNC_INSTANCE_TOKEN)
    private readonly chWithAsyncConfig: ClickHouseClient,
  ) {
    this.chWithAsyncConfig
      .query('SELECT * FROM [TABLE] LIMIT 1')
      .subscribe((row) => console.log('row', row));
  }
}

If you want to define more than one ClickHouseClient using registerAsync method, you will need to create different modules, and inject CLICKHOUSE_ASYNC_INSTANCE_TOKEN into feature modules.

But you don't have to use registerAsync method to create asynchronous ClickHouseClient instances. You can also use custom providers:

import { Inject, Module } from '@nestjs/common';
import { ClickHouseClient } from '@depyronick/nestjs-clickhouse';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: ['.development.env'],
      isGlobal: true,
    }),
  ],
  controllers: [],
  providers: [
    {
      provide: 'CH2',
      useFactory: (config: ConfigService) => {
        return new ClickHouseClient({
          host: config.get('CH2_HOST'),
          database: config.get('CH2_DB'),
          password: config.get('CH2_PWD'),
          username: config.get('CH2_USERNAME'),
        });
      },
      inject: [ConfigService],
    },
  ],
})
export class AppModule {
  constructor(
    @Inject('CH2')
    private readonly clickhouse: ClickHouseClient,
  ) {
    this.clickhouse
      .query('SELECT * FROM [TABLE] LIMIT 1')
      .subscribe((row) => console.log('row', row));
  }
}

With custom providers, you can create as many as asynchronously loaded clients with the name you provided.

Notes

  • This repository will be actively maintained and improved.
  • Planning to implement TCP protocol, if ClickHouse decides to documentate it.
  • Planning to implement inserts with streams.
  • This library supports http response compressions such as brotli, gzip and deflate.

Support

Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.

Stay in touch

License

MIT licensed.

nestjs-clickhouse's People

Contributors

depyronick avatar timkuilman avatar vgorkavenko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

nestjs-clickhouse's Issues

Bump `@depyronick/clickhouse-client` to 2.0.0

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

Describe the solution you'd like

  "dependencies": {
    "@depyronick/clickhouse-client": "^2.0.0"
  }

Teachability, documentation, adoption, migration strategy

What is the motivation / use case for changing the behavior?

Use features from 2.0.0

Inject multiple registerAsync clickhouse clients

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

It was not possible to connect multiple DI clients with async configuration.

Describe the solution you'd like

I made a PR to solve this : #18

Teachability, documentation, adoption, migration strategy

You then just have to set a name in the registerAsync to get the injected client.

What is the motivation / use case for changing the behavior?

Multiple DI clickhouse clients.

Can't add a new record

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

I try to add a new record via POSTMAN

Minimum reproduction code

https://github.com/levkovich2806/testClickHouse

Steps to reproduce

  1. create table

CREATE TABLE messages
                         (
                             user_id UInt32,
                             message String
                         )
                         ENGINE = MergeTree()
                         PRIMARY KEY (user_id)
  1. yarn start
  2. send POST request with JSON data
    curl --header "Content-Type: application/json" --request POST --data '{"user_id":1,"message":"xyz"}' http://localhost:3000

Expected behavior

I want to add new record to a table. But I have an error

Code: 62. DB::Exception: Syntax error: failed at position 1 ('{'): {"user_id":1,"message":"xyz"}. Expected one of: Query, Query with output, EXPLAIN, SELECT query, possibly with UNION, list of union elements, SELECT query, subquery, possibly with UNION, SELECT subquery, SELECT query, WITH, FROM, SELECT, SHOW CREATE QUOTA query, SHOW CREATE, SHOW [FULL] [TEMPORARY] TABLES|DATABASES|CLUSTERS|CLUSTER 'name' [[NOT] [I]LIKE 'str'] [LIMIT expr], SHOW, SHOW ENGINES query, SHOW ENGINES, EXISTS or SHOW CREATE query, EXISTS, DESCRIBE FILESYSTEM CACHE query, DESCRIBE, DESC, DESCRIBE query, SHOW PROCESSLIST query, SHOW PROCESSLIST, CREATE TABLE or ATTACH TABLE query, CREATE, ATTACH, REPLACE, CREATE DATABASE query, CREATE VIEW query, CREATE DICTIONARY, CREATE LIVE VIEW query, CREATE WINDOW VIEW query, ALTER query, ALTER TABLE, ALTER LIVE VIEW, ALTER DATABASE, RENAME query, RENAME TABLE, EXCHANGE TABLES, RENAME DICTIONARY, EXCHANGE DICTIONARIES, RENAME DATABASE, DROP query, DROP, DETACH, TRUNCATE, CHECK TABLE, KILL QUERY query, KILL, OPTIMIZE query, OPTIMIZE TABLE, WATCH query, WATCH, SHOW ACCESS query, SHOW ACCESS, ShowAccessEntitiesQuery, SHOW GRANTS query, SHOW GRANTS, SHOW PRIVILEGES query, SHOW PRIVILEGES, INSERT query, INSERT INTO, USE query, USE, SET ROLE or SET DEFAULT ROLE query, SET ROLE DEFAULT, SET ROLE, SET DEFAULT ROLE, SET query, SET, SYSTEM query, SYSTEM, CREATE USER or ALTER USER query, ALTER USER, CREATE USER, CREATE ROLE or ALTER ROLE query, ALTER ROLE, CREATE ROLE, CREATE QUOTA or ALTER QUOTA query, ALTER QUOTA, CREATE QUOTA, CREATE ROW POLICY or ALTER ROW POLICY query, ALTER POLICY, ALTER ROW POLICY, CREATE POLICY, CREATE ROW POLICY, CREATE SETTINGS PROFILE or ALTER SETTINGS PROFILE query, ALTER SETTINGS PROFILE, ALTER PROFILE, CREATE SETTINGS PROFILE, CREATE PROFILE, CREATE FUNCTION query, DROP FUNCTION query, CREATE NAMED COLLECTION, DROP NAMED COLLECTION query, Alter NAMED COLLECTION query, ALTER, CREATE INDEX query, DROP INDEX query, DROP access entity query, GRANT or REVOKE query, REVOKE, GRANT, EXTERNAL DDL query, EXTERNAL DDL FROM, TCL query, BEGIN TRANSACTION, COMMIT, ROLLBACK, SET TRANSACTION SNAPSHOT, Delete query, DELETE, BACKUP or RESTORE query, BACKUP, RESTORE. (SYNTAX_ERROR) (version 23.3.1.2399 (official build))

Package version

^2.0.1

NestJS version

^9.0.0

Node.js version

v16.14.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Compatibility with @nestjs/common >= 9.0.0

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

When using @nestjs/common >= 9.0.0, cannot install this lib without forcing --legacy-peer-deps

Minimum reproduction code

https://github.com/depyronick/nestjs-clickhouse

Steps to reproduce

npm i !

Expected behavior

Install without deps error

Package version

1.0.15

NestJS version

9.0.3

Node.js version

16.16.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Add ClickHouseModule.registerAsync

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

At the moment, there is no way to do asynchronous client registration in order to transfer the config from ConfigService to it

Describe the solution you'd like

    ClickHouseModule.registerAsync({
      useFactory: (config: ConfigService) => config.get("chOptions"),
      inject: [ConfigService],
    })

Teachability, documentation, adoption, migration strategy

Not necessary

What is the motivation / use case for changing the behavior?

Not necessary

useFactory

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

Problems with types useFactory. All keys are optional in the config except the "httpConfig" key. This key is mandatory, but there is no information about where to get the data that needs to be placed there. There is also no information in the documentation. And the example from the documentation causes an error.

Minimum reproduction code

https://github.com/

Steps to reproduce

No response

Expected behavior

Need to find out where to get the data for httpConfig

Package version

2.0.0

NestJS version

No response

Node.js version

No response

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Update Clickhouse client to 2.0.3

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

The underlying lib had a problem in 2.0.2, issue is described here.

Describe the solution you'd like

Update the clickhouse client dependency to the latest version.

Teachability, documentation, adoption, migration strategy

Not applicable.

What is the motivation / use case for changing the behavior?

Not applicable.

Unable to insert data

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

I use an 'insert' method but get this:

'Code: 62. DB::Exception: Syntax error: failed at position 1 ('{'): {"id":1,"type":"test","from":"123","body":"test","params":"test","httpCode":123}. Expected one of: Query, Query with output, EXPLAIN, SELECT query, possibly with UNION, list of union elements, SELECT query, subquery, possibly with UNION, SELECT subquery, SELECT query, WITH, SELECT, SHOW CREATE QUOTA query, SHOW CREATE, SHOW [TEMPORARY] TABLES|DATABASES|CLUSTERS|CLUSTER 'name' [[NOT] [I]LIKE 'str'] [LIMIT expr], SHOW, EXISTS or SHOW CREATE query, EXISTS, DESCRIBE query, DESCRIBE, DESC, SHOW PROCESSLIST query, SHOW PROCESSLIST, CREATE TABLE or ATTACH TABLE query, CREATE, ATTACH, REPLACE, CREATE DATABASE query, CREATE VIEW query, CREATE DICTIONARY, CREATE LIVE VIEW query, CREATE WINDOW VIEW query, ALTER query, ALTER TABLE, ALTER LIVE VIEW, ALTER DATABASE, RENAME query, RENAME TABLE, EXCHANGE TABLES, RENAME DICTIONARY, EXCHANGE DICTIONARIES, RENAME DATABASE, DROP query, DROP, DETACH, TRUNCATE, CHECK TABLE, KILL QUERY query, KILL, OPTIMIZE query, OPTIMIZE TABLE, WATCH query, WATCH, SHOW ACCESS query, SHOW ACCESS, ShowAccessEntitiesQuery, SHOW GRANTS
query, SHOW GRANTS, SHOW PRIVILEGES query, SHOW PRIVILEGES, INSERT query, INSERT INTO, USE query, USE, SET ROLE or SET DEFAULT ROLE query, SET ROLE DEFAULT, SET ROLE, SET DEFAULT ROLE, SET query, SET, SYSTEM query, SYSTEM, CREATE USER or ALTER USER query, ALTER USER, CREATE USER, CREATE ROLE or ALTER ROLE query, ALTER ROLE, CREATE ROLE, CREATE QUOTA or ALTER QUOTA query, ALTER QUOTA, CREATE QUOTA, CREATE ROW POLICY or ALTER ROW POLICY query, ALTER POLICY, ALTER ROW POLICY, CREATE POLICY, CREATE ROW POLICY, CREATE SETTINGS PROFILE or ALTER SETTINGS PROFILE query, ALTER SETTINGS PROFILE, ALTER PROFILE, CREATE SETTINGS PROFILE, CREATE PROFILE, CREATE FUNCTION query, DROP FUNCTION query, DROP access entity query, GRANT or REVOKE query, REVOKE, GRANT, EXTERNAL DDL query, EXTERNAL DDL FROM, BACKUP or RESTORE query, BACKUP, RESTORE. (SYNTAX_ERROR) (version 22.1.3.7 (official build))'

I have attached a link to my sandbox project where the issue can be seen. As the project is a sandbox I apologize for some little dirty mess in there

Minimum reproduction code

https://github.com/MrTomSawyer/message-publisher/blob/master/src/app.service.ts

Steps to reproduce

  1. Create a docker clickhouse container:
    docker run -d --name clickhouse-server -e CLICKHOUSE_USER=guest -e CLICKHOUSE_PASSWORD=guest -p 8123:8123 yandex/clickhouse-server

  2. Clone and start my project

  3. Go to postman and hit GET localhost:3001/click

Expected behavior

I expect data to be inserted into clickhouse with no errors

Package version

latest

NestJS version

9.0.0

Node.js version

18.15.0

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

No response

Support migration database

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

I want to this lib have a migration data how can I do that pls, I'm searching but can see this feature is available

Describe the solution you'd like

Does it need to use any lib to create migration I'll support and create PR for you

Teachability, documentation, adoption, migration strategy

Yub like Typeform or other click house lib https://github.com/microfleet/clickhouse-adapter

What is the motivation / use case for changing the behavior?

Yub easy to init database

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.