Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

V súčasnosti sa REST API stalo štandardom pre vývoj webových aplikácií, čo umožňuje rozdeliť vývoj na samostatné časti. Pre UI sa v súčasnosti používajú rôzne populárne frameworky ako Angular, React, Vue a iné. Vývojári backendu si môžu vybrať zo širokej škály jazykov a rámcov. Dnes by som chcel hovoriť o takom rámci, ako je NestJS. Sme v tom TestMace Aktívne ho využívame na interné projekty. Použitie hniezda a balenia @nestjsx/crud, vytvoríme jednoduchú aplikáciu CRUD.

Prečo NestJS

V poslednej dobe sa v komunite JavaScriptu objavilo pomerne veľa backendových rámcov. A ak z hľadiska funkcionality poskytujú podobné možnosti ako Nest, tak v jednej veci rozhodne vyhráva – toto je architektúra. Nasledujúce funkcie NestJS vám umožňujú vytvárať priemyselné aplikácie a škálovať vývoj pre veľké tímy:

  • pomocou TypeScript ako hlavného vývojového jazyka. Hoci NestJS podporuje JavaScript, niektoré funkcie nemusia fungovať, najmä ak hovoríme o balíkoch tretích strán;
  • prítomnosť DI kontajnera, ktorý vám umožňuje vytvárať voľne spojené komponenty;
  • Funkcionalita samotného rámca je rozdelená na nezávislé vymeniteľné komponenty. Napríklad pod kapotou ako rám môže byť použitý ako expresnéA postiť sa, na prácu s databázou, nest of the box poskytuje väzby na typorm, mongoose, pokračovať;
  • NestJS je platformovo agnostický a podporuje REST, GraphQL, Websockets, gRPC atď.

Samotný framework je inšpirovaný Angular frontend frameworkom a má s ním koncepčne veľa spoločného.

Inštalácia NestJS a nasadenie projektu

Hniezdo obsahuje balenie hniezdo/cli, ktorý vám umožňuje rýchlo nasadiť základný aplikačný rámec. Nainštalujte tento balík globálne:

npm install --global @nest/cli

Po inštalácii si vygenerujeme základný framework našej aplikácie s názvom nest-rest. To sa vykonáva pomocou príkazu nest new nest-rest.

hniezdo nové hniezdo-rest

dmitrii@dmitrii-HP-ZBook-17-G3:~/projects $ nest new nest-rest
  We will scaffold your app in a few seconds..

CREATE /nest-rest/.prettierrc (51 bytes)
CREATE /nest-rest/README.md (3370 bytes)
CREATE /nest-rest/nest-cli.json (84 bytes)
CREATE /nest-rest/nodemon-debug.json (163 bytes)
CREATE /nest-rest/nodemon.json (67 bytes)
CREATE /nest-rest/package.json (1805 bytes)
CREATE /nest-rest/tsconfig.build.json (97 bytes)
CREATE /nest-rest/tsconfig.json (325 bytes)
CREATE /nest-rest/tslint.json (426 bytes)
CREATE /nest-rest/src/app.controller.spec.ts (617 bytes)
CREATE /nest-rest/src/app.controller.ts (274 bytes)
CREATE /nest-rest/src/app.module.ts (249 bytes)
CREATE /nest-rest/src/app.service.ts (142 bytes)
CREATE /nest-rest/src/main.ts (208 bytes)
CREATE /nest-rest/test/app.e2e-spec.ts (561 bytes)
CREATE /nest-rest/test/jest-e2e.json (183 bytes)

? Which package manager would you ️ to use? yarn
 Installation in progress... 

  Successfully created project nest-rest
  Get started with the following commands:

$ cd nest-rest
$ yarn run start

                          Thanks for installing Nest 
                 Please consider donating to our open collective
                        to help us maintain this package.

                 Donate: https://opencollective.com/nest

Ako správcu balíkov si vyberieme priadzu.
V tomto bode môžete spustiť server pomocou príkazu npm start a ísť na adresu http://localhost:3000 môžete vidieť hlavnú stránku. Nie preto sme sa tu však zišli a ideme ďalej.

Nastavenie práce s databázou

Ako DBMS pre tento článok som si vybral PostrgreSQL. O chuti nie je spor, podľa môjho názoru je to najvyspelejší DBMS, ktorý má všetky potrebné schopnosti. Ako už bolo spomenuté, Nest poskytuje integráciu s rôznymi balíkmi na prácu s databázami. Pretože Keďže moja voľba padla na PostgreSQL, bolo by logické zvoliť ako ORM TypeORM. Nainštalujte potrebné balíčky pre integráciu s databázou:

yarn add typeorm @nestjs/typeorm pg

V poradí, na čo je každý balík potrebný:

  1. typorm - balík priamo zo samotného ORM;
  2. @nestjs/typeorm – balík TypeORM pre NestJS. Pridáva moduly na import do projektových modulov, ako aj sadu pomocných dekoratérov;
  3. pg - ovládač pre prácu s PostgreSQL.

Dobre, balíky sú nainštalované, teraz musíte spustiť samotnú databázu. Na nasadenie databázy použijem docker-compose.yml s nasledujúcim obsahom:

prístavný robotník-compose.yml

version: '3.1'

services:
  db:
    image: postgres:11.2
    restart: always
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - ../db:/var/lib/postgresql/data
      - ./postgresql.conf:/etc/postgresql/postgresql.conf
    ports:
      - 5432:5432
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

Ako vidíte, tento súbor konfiguruje spustenie 2 kontajnerov:

  1. db je kontajner priamo obsahujúci databázu. V našom prípade sa používa postgresql verzia 11.2;
  2. správca – správca databázy. Poskytuje webové rozhranie na prezeranie a správu databázy.

Pre prácu s tcp pripojeniami som pridal nasledujúcu konfiguráciu.

postgresql.conf

# -----------------------------
# PostgreSQL configuration file
# -----------------------------
#
# This file consists of lines of the form:
#
#   name = value
#
# (The "=" is optional.)  Whitespace may be used.  Comments are introduced with
# "#" anywhere on a line.  The complete list of parameter names and allowed
# values can be found in the PostgreSQL documentation.
#
# The commented-out settings shown in this file represent the default values.
# Re-commenting a setting is NOT sufficient to revert it to the default value;
# you need to reload the server.
#
# This file is read on server startup and when the server receives a SIGHUP
# signal.  If you edit the file on a running system, you have to SIGHUP the
# server for the changes to take effect, run "pg_ctl reload", or execute
# "SELECT pg_reload_conf()".  Some parameters, which are marked below,
# require a server shutdown and restart to take effect.
#
# Any parameter can also be given as a command-line option to the server, e.g.,
# "postgres -c log_connections=on".  Some parameters can be changed at run time
# with the "SET" SQL command.
#
# Memory units:  kB = kilobytes        Time units:  ms  = milliseconds
#                MB = megabytes                     s   = seconds
#                GB = gigabytes                     min = minutes
#                TB = terabytes                     h   = hours
#                                                   d   = days
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
# The default values of these variables are driven from the -D command-line
# option or PGDATA environment variable, represented here as ConfigDir.
#data_directory = 'ConfigDir'       # use data in another directory
# (change requires restart)
#hba_file = 'ConfigDir/pg_hba.conf' # host-based authentication file
# (change requires restart)
#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file
# (change requires restart)
# If external_pid_file is not explicitly set, no extra PID file is written.
#external_pid_file = ''         # write an extra PID file
# (change requires restart)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------
# - Connection Settings -
listen_addresses = '*'
#listen_addresses = 'localhost'     # what IP address(es) to listen on;
# comma-separated list of addresses;
# defaults to 'localhost'; use '*' for all
# (change requires restart)
#port = 5432                # (change requires restart)
#max_connections = 100          # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
#unix_socket_directories = '/tmp'   # comma-separated list of directories
# (change requires restart)
#unix_socket_group = ''         # (change requires restart)
#unix_socket_permissions = 0777     # begin with 0 to use octal notation
# (change requires restart)
#bonjour = off              # advertise server via Bonjour
# (change requires restart)
#bonjour_name = ''          # defaults to the computer name
# (change requires restart)
# - TCP Keepalives -
# see "man 7 tcp" for details
#tcp_keepalives_idle = 0        # TCP_KEEPIDLE, in seconds;
# 0 selects the system default
#tcp_keepalives_interval = 0        # TCP_KEEPINTVL, in seconds;
# 0 selects the system default
#tcp_keepalives_count = 0       # TCP_KEEPCNT;
# 0 selects the system default
# - Authentication -
#authentication_timeout = 1min      # 1s-600s
#password_encryption = md5      # md5 or scram-sha-256
#db_user_namespace = off
# GSSAPI using Kerberos
#krb_server_keyfile = ''
#krb_caseins_users = off
# - SSL -
#ssl = off
#ssl_ca_file = ''
#ssl_cert_file = 'server.crt'
#ssl_crl_file = ''
#ssl_key_file = 'server.key'
#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_min_protocol_version = 'TLSv1'
#ssl_max_protocol_version = ''
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------
# - Memory -
#shared_buffers = 32MB          # min 128kB
# (change requires restart)
#huge_pages = try           # on, off, or try
# (change requires restart)
#temp_buffers = 8MB         # min 800kB
#max_prepared_transactions = 0      # zero disables the feature
# (change requires restart)
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
# you actively intend to use prepared transactions.
#work_mem = 4MB             # min 64kB
#maintenance_work_mem = 64MB        # min 1MB
#autovacuum_work_mem = -1       # min 1MB, or -1 to use maintenance_work_mem
#max_stack_depth = 2MB          # min 100kB
#shared_memory_type = mmap      # the default is the first option
# supported by the operating system:
#   mmap
#   sysv
#   windows
# (change requires restart)
#dynamic_shared_memory_type = posix # the default is the first option
# supported by the operating system:
#   posix
#   sysv
#   windows
#   mmap
# (change requires restart)
# - Disk -
#temp_file_limit = -1           # limits per-process temp file space
# in kB, or -1 for no limit
# - Kernel Resources -
#max_files_per_process = 1000       # min 25
# (change requires restart)
# - Cost-Based Vacuum Delay -
#vacuum_cost_delay = 0          # 0-100 milliseconds (0 disables)
#vacuum_cost_page_hit = 1       # 0-10000 credits
#vacuum_cost_page_miss = 10     # 0-10000 credits
#vacuum_cost_page_dirty = 20        # 0-10000 credits
#vacuum_cost_limit = 200        # 1-10000 credits
# - Background Writer -
#bgwriter_delay = 200ms         # 10-10000ms between rounds
#bgwriter_lru_maxpages = 100        # max buffers written/round, 0 disables
#bgwriter_lru_multiplier = 2.0      # 0-10.0 multiplier on buffers scanned/round
#bgwriter_flush_after = 0       # measured in pages, 0 disables
# - Asynchronous Behavior -
#effective_io_concurrency = 1       # 1-1000; 0 disables prefetching
#max_worker_processes = 8       # (change requires restart)
#max_parallel_maintenance_workers = 2   # taken from max_parallel_workers
#max_parallel_workers_per_gather = 2    # taken from max_parallel_workers
#parallel_leader_participation = on
#max_parallel_workers = 8       # maximum number of max_worker_processes that
# can be used in parallel operations
#old_snapshot_threshold = -1        # 1min-60d; -1 disables; 0 is immediate
# (change requires restart)
#backend_flush_after = 0        # measured in pages, 0 disables
#------------------------------------------------------------------------------
# WRITE-AHEAD LOG
#------------------------------------------------------------------------------
# - Settings -
#wal_level = replica            # minimal, replica, or logical
# (change requires restart)
#fsync = on             # flush data to disk for crash safety
# (turning this off can cause
# unrecoverable data corruption)
#synchronous_commit = on        # synchronization level;
# off, local, remote_write, remote_apply, or on
#wal_sync_method = fsync        # the default is the first option
# supported by the operating system:
#   open_datasync
#   fdatasync (default on Linux)
#   fsync
#   fsync_writethrough
#   open_sync
#full_page_writes = on          # recover from partial page writes
#wal_compression = off          # enable compression of full-page writes
#wal_log_hints = off            # also do full page writes of non-critical updates
# (change requires restart)
#wal_buffers = -1           # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms       # 1-10000 milliseconds
#wal_writer_flush_after = 1MB       # measured in pages, 0 disables
#commit_delay = 0           # range 0-100000, in microseconds
#commit_siblings = 5            # range 1-1000
# - Checkpoints -
#checkpoint_timeout = 5min      # range 30s-1d
#max_wal_size = 1GB
#min_wal_size = 80MB
#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0
#checkpoint_flush_after = 0     # measured in pages, 0 disables
#checkpoint_warning = 30s       # 0 disables
# - Archiving -
#archive_mode = off     # enables archiving; off, on, or always
# (change requires restart)
#archive_command = ''       # command to use to archive a logfile segment
# placeholders: %p = path of file to archive
#               %f = file name only
# e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'
#archive_timeout = 0        # force a logfile segment switch after this
# number of seconds; 0 disables
# - Archive Recovery -
# These are only used in recovery mode.
#restore_command = ''       # command to use to restore an archived logfile segment
# placeholders: %p = path of file to restore
#               %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
# (change requires restart)
#archive_cleanup_command = ''   # command to execute at every restartpoint
#recovery_end_command = ''  # command to execute at completion of recovery
# - Recovery Target -
# Set these only when performing a targeted recovery.
#recovery_target = ''       # 'immediate' to end recovery as soon as a
# consistent state is reached
# (change requires restart)
#recovery_target_name = ''  # the named restore point to which recovery will proceed
# (change requires restart)
#recovery_target_time = ''  # the time stamp up to which recovery will proceed
# (change requires restart)
#recovery_target_xid = ''   # the transaction ID up to which recovery will proceed
# (change requires restart)
#recovery_target_lsn = ''   # the WAL LSN up to which recovery will proceed
# (change requires restart)
#recovery_target_inclusive = on # Specifies whether to stop:
# just after the specified recovery target (on)
# just before the recovery target (off)
# (change requires restart)
#recovery_target_timeline = 'latest'    # 'current', 'latest', or timeline ID
# (change requires restart)
#recovery_target_action = 'pause'   # 'pause', 'promote', 'shutdown'
# (change requires restart)
#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------
# - Sending Servers -
# Set these on the master and on any standby that will send replication data.
#max_wal_senders = 10       # max number of walsender processes
# (change requires restart)
#wal_keep_segments = 0      # in logfile segments; 0 disables
#wal_sender_timeout = 60s   # in milliseconds; 0 disables
#max_replication_slots = 10 # max number of replication slots
# (change requires restart)
#track_commit_timestamp = off   # collect timestamp of transaction commit
# (change requires restart)
# - Master Server -
# These settings are ignored on a standby server.
#synchronous_standby_names = '' # standby servers that provide sync rep
# method to choose sync standbys, number of sync standbys,
# and comma-separated list of application_name
# from standby(s); '*' = all
#vacuum_defer_cleanup_age = 0   # number of xacts by which cleanup is delayed
# - Standby Servers -
# These settings are ignored on a master server.
#primary_conninfo = ''          # connection string to sending server
# (change requires restart)
#primary_slot_name = ''         # replication slot on sending server
# (change requires restart)
#promote_trigger_file = ''      # file name whose presence ends recovery
#hot_standby = on           # "off" disallows queries during recovery
# (change requires restart)
#max_standby_archive_delay = 30s    # max delay before canceling queries
# when reading WAL from archive;
# -1 allows indefinite delay
#max_standby_streaming_delay = 30s  # max delay before canceling queries
# when reading streaming WAL;
# -1 allows indefinite delay
#wal_receiver_status_interval = 10s # send replies at least this often
# 0 disables
#hot_standby_feedback = off     # send info from standby to prevent
# query conflicts
#wal_receiver_timeout = 60s     # time that receiver waits for
# communication from master
# in milliseconds; 0 disables
#wal_retrieve_retry_interval = 5s   # time to wait before retrying to
# retrieve WAL after a failed attempt
#recovery_min_apply_delay = 0       # minimum delay for applying changes during recovery
# - Subscribers -
# These settings are ignored on a publisher.
#max_logical_replication_workers = 4    # taken from max_worker_processes
# (change requires restart)
#max_sync_workers_per_subscription = 2  # taken from max_logical_replication_workers
#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------
# - Planner Method Configuration -
#enable_bitmapscan = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
#enable_indexonlyscan = on
#enable_material = on
#enable_mergejoin = on
#enable_nestloop = on
#enable_parallel_append = on
#enable_seqscan = on
#enable_sort = on
#enable_tidscan = on
#enable_partitionwise_join = off
#enable_partitionwise_aggregate = off
#enable_parallel_hash = on
#enable_partition_pruning = on
# - Planner Cost Constants -
#seq_page_cost = 1.0            # measured on an arbitrary scale
#random_page_cost = 4.0         # same scale as above
#cpu_tuple_cost = 0.01          # same scale as above
#cpu_index_tuple_cost = 0.005       # same scale as above
#cpu_operator_cost = 0.0025     # same scale as above
#parallel_tuple_cost = 0.1      # same scale as above
#parallel_setup_cost = 1000.0   # same scale as above
#jit_above_cost = 100000        # perform JIT compilation if available
# and query more expensive than this;
# -1 disables
#jit_inline_above_cost = 500000     # inline small functions if query is
# more expensive than this; -1 disables
#jit_optimize_above_cost = 500000   # use expensive JIT optimizations if
# query is more expensive than this;
# -1 disables
#min_parallel_table_scan_size = 8MB
#min_parallel_index_scan_size = 512kB
#effective_cache_size = 4GB
# - Genetic Query Optimizer -
#geqo = on
#geqo_threshold = 12
#geqo_effort = 5            # range 1-10
#geqo_pool_size = 0         # selects default based on effort
#geqo_generations = 0           # selects default based on effort
#geqo_selection_bias = 2.0      # range 1.5-2.0
#geqo_seed = 0.0            # range 0.0-1.0
# - Other Planner Options -
#default_statistics_target = 100    # range 1-10000
#constraint_exclusion = partition   # on, off, or partition
#cursor_tuple_fraction = 0.1        # range 0.0-1.0
#from_collapse_limit = 8
#join_collapse_limit = 8        # 1 disables collapsing of explicit
# JOIN clauses
#force_parallel_mode = off
#jit = on               # allow JIT compilation
#plan_cache_mode = auto         # auto, force_generic_plan or
# force_custom_plan
#------------------------------------------------------------------------------
# REPORTING AND LOGGING
#------------------------------------------------------------------------------
# - Where to Log -
#log_destination = 'stderr'     # Valid values are combinations of
# stderr, csvlog, syslog, and eventlog,
# depending on platform.  csvlog
# requires logging_collector to be on.
# This is used when logging to stderr:
#logging_collector = off        # Enable capturing of stderr and csvlog
# into log files. Required to be on for
# csvlogs.
# (change requires restart)
# These are only used if logging_collector is on:
#log_directory = 'log'          # directory where log files are written,
# can be absolute or relative to PGDATA
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'    # log file name pattern,
# can include strftime() escapes
#log_file_mode = 0600           # creation mode for log files,
# begin with 0 to use octal notation
#log_truncate_on_rotation = off     # If on, an existing log file with the
# same name as the new log file will be
# truncated rather than appended to.
# But such truncation only occurs on
# time-driven rotation, not on restarts
# or size-driven rotation.  Default is
# off, meaning append to existing files
# in all cases.
#log_rotation_age = 1d          # Automatic rotation of logfiles will
# happen after that time.  0 disables.
#log_rotation_size = 10MB       # Automatic rotation of logfiles will
# happen after that much log output.
# 0 disables.
# These are relevant when logging to syslog:
#syslog_facility = 'LOCAL0'
#syslog_ident = 'postgres'
#syslog_sequence_numbers = on
#syslog_split_messages = on
# This is only relevant when logging to eventlog (win32):
# (change requires restart)
#event_source = 'PostgreSQL'
# - When to Log -
#log_min_messages = warning     # values in order of decreasing detail:
#   debug5
#   debug4
#   debug3
#   debug2
#   debug1
#   info
#   notice
#   warning
#   error
#   log
#   fatal
#   panic
#log_min_error_statement = error    # values in order of decreasing detail:
#   debug5
#   debug4
#   debug3
#   debug2
#   debug1
#   info
#   notice
#   warning
#   error
#   log
#   fatal
#   panic (effectively off)
#log_min_duration_statement = -1    # logs statements and their durations
# according to log_statement_sample_rate. -1 is disabled,
# 0 logs all statement, > 0 logs only statements running at
# least this number of milliseconds.
#log_statement_sample_rate = 1  # Fraction of logged statements over
# log_min_duration_statement. 1.0 logs all statements,
# 0 never logs.
# - What to Log -
#debug_print_parse = off
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = on
#log_checkpoints = off
#log_connections = off
#log_disconnections = off
#log_duration = off
#log_error_verbosity = default      # terse, default, or verbose messages
#log_hostname = off
#log_line_prefix = '%m [%p] '       # special values:
#   %a = application name
#   %u = user name
#   %d = database name
#   %r = remote host and port
#   %h = remote host
#   %p = process ID
#   %t = timestamp without milliseconds
#   %m = timestamp with milliseconds
#   %n = timestamp with milliseconds (as a Unix epoch)
#   %i = command tag
#   %e = SQL state
#   %c = session ID
#   %l = session line number
#   %s = session start timestamp
#   %v = virtual transaction ID
#   %x = transaction ID (0 if none)
#   %q = stop here in non-session
#        processes
#   %% = '%'
# e.g. '<%u%%%d> '
#log_lock_waits = off           # log lock waits >= deadlock_timeout
#log_statement = 'none'         # none, ddl, mod, all
#log_replication_commands = off
#log_temp_files = -1            # log temporary files equal or larger
# than the specified size in kilobytes;
# -1 disables, 0 logs all temp files
#log_timezone = 'GMT'
#------------------------------------------------------------------------------
# PROCESS TITLE
#------------------------------------------------------------------------------
#cluster_name = ''          # added to process titles if nonempty
# (change requires restart)
#update_process_title = on
#------------------------------------------------------------------------------
# STATISTICS
#------------------------------------------------------------------------------
# - Query and Index Statistics Collector -
#track_activities = on
#track_counts = on
#track_io_timing = off
#track_functions = none         # none, pl, all
#track_activity_query_size = 1024   # (change requires restart)
#stats_temp_directory = 'pg_stat_tmp'
# - Monitoring -
#log_parser_stats = off
#log_planner_stats = off
#log_executor_stats = off
#log_statement_stats = off
#------------------------------------------------------------------------------
# AUTOVACUUM
#------------------------------------------------------------------------------
#autovacuum = on            # Enable autovacuum subprocess?  'on'
# requires track_counts to also be on.
#log_autovacuum_min_duration = -1   # -1 disables, 0 logs all actions and
# their durations, > 0 logs only
# actions running at least this number
# of milliseconds.
#autovacuum_max_workers = 3     # max number of autovacuum subprocesses
# (change requires restart)
#autovacuum_naptime = 1min      # time between autovacuum runs
#autovacuum_vacuum_threshold = 50   # min number of row updates before
# vacuum
#autovacuum_analyze_threshold = 50  # min number of row updates before
# analyze
#autovacuum_vacuum_scale_factor = 0.2   # fraction of table size before vacuum
#autovacuum_analyze_scale_factor = 0.1  # fraction of table size before analyze
#autovacuum_freeze_max_age = 200000000  # maximum XID age before forced vacuum
# (change requires restart)
#autovacuum_multixact_freeze_max_age = 400000000    # maximum multixact age
# before forced vacuum
# (change requires restart)
#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for
# autovacuum, in milliseconds;
# -1 means use vacuum_cost_delay
#autovacuum_vacuum_cost_limit = -1  # default vacuum cost limit for
# autovacuum, -1 means use
# vacuum_cost_limit
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------
# - Statement Behavior -
#client_min_messages = notice       # values in order of decreasing detail:
#   debug5
#   debug4
#   debug3
#   debug2
#   debug1
#   log
#   notice
#   warning
#   error
#search_path = '"$user", public'    # schema names
#row_security = on
#default_tablespace = ''        # a tablespace name, '' uses the default
#temp_tablespaces = ''          # a list of tablespace names, '' uses
# only default tablespace
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#default_transaction_deferrable = off
#session_replication_role = 'origin'
#statement_timeout = 0          # in milliseconds, 0 is disabled
#lock_timeout = 0           # in milliseconds, 0 is disabled
#idle_in_transaction_session_timeout = 0    # in milliseconds, 0 is disabled
#vacuum_freeze_min_age = 50000000
#vacuum_freeze_table_age = 150000000
#vacuum_multixact_freeze_min_age = 5000000
#vacuum_multixact_freeze_table_age = 150000000
#vacuum_cleanup_index_scale_factor = 0.1    # fraction of total number of tuples
# before index cleanup, 0 always performs
# index cleanup
#bytea_output = 'hex'           # hex, escape
#xmlbinary = 'base64'
#xmloption = 'content'
#gin_fuzzy_search_limit = 0
#gin_pending_list_limit = 4MB
# - Locale and Formatting -
#datestyle = 'iso, mdy'
#intervalstyle = 'postgres'
#timezone = 'GMT'
#timezone_abbreviations = 'Default'     # Select the set of available time zone
# abbreviations.  Currently, there are
#   Default
#   Australia (historical usage)
#   India
# You can create your own file in
# share/timezonesets/.
#extra_float_digits = 1         # min -15, max 3; any value >0 actually
# selects precise output mode
#client_encoding = sql_ascii        # actually, defaults to database
# encoding
# These settings are initialized by initdb, but they can be changed.
#lc_messages = 'C'          # locale for system error message
# strings
#lc_monetary = 'C'          # locale for monetary formatting
#lc_numeric = 'C'           # locale for number formatting
#lc_time = 'C'              # locale for time formatting
# default configuration for text search
#default_text_search_config = 'pg_catalog.simple'
# - Shared Library Preloading -
#shared_preload_libraries = ''  # (change requires restart)
#local_preload_libraries = ''
#session_preload_libraries = ''
#jit_provider = 'llvmjit'       # JIT library to use
# - Other Defaults -
#dynamic_library_path = '$libdir'
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------
#deadlock_timeout = 1s
#max_locks_per_transaction = 64     # min 10
# (change requires restart)
#max_pred_locks_per_transaction = 64    # min 10
# (change requires restart)
#max_pred_locks_per_relation = -2   # negative values mean
# (max_pred_locks_per_transaction
#  / -max_pred_locks_per_relation) - 1
#max_pred_locks_per_page = 2            # min 0
#------------------------------------------------------------------------------
# VERSION AND PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------
# - Previous PostgreSQL Versions -
#array_nulls = on
#backslash_quote = safe_encoding    # on, off, or safe_encoding
#escape_string_warning = on
#lo_compat_privileges = off
#operator_precedence_warning = off
#quote_all_identifiers = off
#standard_conforming_strings = on
#synchronize_seqscans = on
# - Other Platforms and Clients -
#transform_null_equals = off
#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------
#exit_on_error = off            # terminate session on any error?
#restart_after_crash = on       # reinitialize after backend crash?
#data_sync_retry = off          # retry or panic on failure to fsync
# data?
# (change requires restart)
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
# These options allow settings to be loaded from files other than the
# default postgresql.conf.
#include_dir = 'conf.d'         # include files ending in '.conf' from
# directory 'conf.d'
#include_if_exists = 'exists.conf'  # include file only if it exists
#include = 'special.conf'       # include file
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here

To je všetko, pomocou príkazu môžete spustiť kontajnery docker-compose up -d. Alebo v samostatnej konzole s príkazom docker-compose up.

Balíčky sú teda nainštalované, databáza spustená, ostáva už len ich skamarátiť. Ak to chcete urobiť, musíte do koreňového adresára projektu pridať nasledujúci súbor: ormconfig.js:

ormconfig.js

const process = require('process');
const username = process.env.POSTGRES_USER || "postgres";
const password = process.env.POSTGRES_PASSWORD || "example";
module.exports = {
"type": "postgres",
"host": "localhost",
"port": 5432,
username,
password,
"database": "postgres",
"synchronize": true,
"dropSchema": false,
"logging": true,
"entities": [__dirname + "/src/**/*.entity.ts", __dirname + "/dist/**/*.entity.js"],
"migrations": ["migrations/**/*.ts"],
"subscribers": ["subscriber/**/*.ts", "dist/subscriber/**/.js"],
"cli": {
"entitiesDir": "src",
"migrationsDir": "migrations",
"subscribersDir": "subscriber"
}
}

Táto konfigurácia sa použije pre typ cli.

Pozrime sa na túto konfiguráciu podrobnejšie. Na riadkoch 3 a 4 získame používateľské meno a heslo z premenných prostredia. Je to výhodné, keď máte niekoľko prostredí (dev, stage, prod atď.). V predvolenom nastavení je používateľské meno postgres a heslo je príklad. Zvyšok konfigurácie je triviálny, takže sa zameriame len na tie najzaujímavejšie parametre:

  • synchronizovať - ​​Označuje, či sa má pri spustení aplikácie automaticky vytvoriť schéma databázy. Pri tejto možnosti buďte opatrní a nepoužívajte ju vo výrobe, inak prídete o dáta. Táto možnosť je vhodná pri vývoji a ladení aplikácie. Ako alternatívu k tejto možnosti môžete použiť príkaz schema:sync z CLI TypeORM.
  • dropSchema - resetovanie schémy pri každom nadviazaní spojenia. Rovnako ako predchádzajúca, aj táto možnosť by sa mala používať iba pri vývoji a ladení aplikácie.
  • entity – aké cesty hľadať popisy modelov. Upozorňujeme, že je podporované vyhľadávanie podľa masky.
  • cli.entitiesDir je adresár, do ktorého by sa štandardne mali ukladať modely vytvorené z TypeORM CLI.

Aby sme mohli využívať všetky funkcie TypeORM v našej aplikácii Nest, musíme modul importovať TypeOrmModule в AppModule. Tie. tvoj AppModule bude vyzerať takto:

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as process from "process";
const username = process.env.POSTGRES_USER || 'postgres';
const password = process.env.POSTGRES_PASSWORD || 'example';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username,
password,
database: 'postgres',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

Ako ste si mohli všimnúť, metóda forRoot prenesie sa rovnaká konfigurácia pre prácu s databázou ako v súbore ormconfig.ts

Zostáva posledný dotyk - pridať niekoľko úloh pre prácu s TypeORM v package.json. Faktom je, že CLI je napísané v javascripte a beží v prostredí nodejs. Všetky naše modely a migrácie však budú napísané strojopisom. Preto je potrebné pred použitím CLI transpilovať naše migrácie a modely. Na to potrebujeme balík ts-node:

yarn add -D ts-node

Potom pridajte potrebné príkazy do package.json:

"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
"migration:generate": "yarn run typeorm migration:generate -n",
"migration:create": "yarn run typeorm migration:create -n",
"migration:run": "yarn run typeorm migration:run"

Prvý príkaz, typeorm, pridáva obal ts-node na spustenie TypeORM cli. Zostávajúce príkazy sú pohodlné skratky, ktoré ako vývojár budete používať takmer každý deň:
migration:generate — vytváranie migrácií na základe zmien vo vašich modeloch.
migration:create — vytvorenie prázdnej migrácie.
migration:run — spustenie migrácie.
Teraz je to všetko, pridali sme potrebné balíčky, nakonfigurovali aplikáciu na prácu s databázou z cli aj zo samotnej aplikácie a tiež sme spustili DBMS. Je čas pridať do našej aplikácie logiku.

Inštalácia balíkov na vytváranie CRUD

Iba pomocou Nest môžete vytvoriť API, ktoré vám umožní vytvárať, čítať, aktualizovať a odstraňovať entitu. Toto riešenie bude maximálne flexibilné, no v niektorých prípadoch bude nadbytočné. Napríklad, ak potrebujete rýchlo vytvoriť prototyp, často môžete obetovať flexibilitu pre rýchlosť vývoja. Mnohé rámce poskytujú funkcie na generovanie CRUD popisom dátového modelu určitej entity. A Nest nie je výnimkou! Túto funkciu poskytuje balík @nestjsx/crud. Jeho možnosti sú veľmi zaujímavé:

  • jednoduchá inštalácia a konfigurácia;
  • nezávislosť DBMS;
  • výkonný dopytovací jazyk so schopnosťou filtrovať, stránkovať, triediť, načítať vzťahy a vnorené entity, ukladať do vyrovnávacej pamäte atď.;
  • balík na generovanie požiadaviek na front-ende;
  • jednoduché ovládanie metód ovládača;
  • malá konfigurácia;
  • podpora dokumentácie swagger.

Funkcionalita je rozdelená do niekoľkých balíkov:

  • @nestjsx/crud - základný balík, ktorý dekoratér poskytuje Crud() na generovanie, konfiguráciu a validáciu trasy;
  • @nestjsx/crud-request — balík, ktorý poskytuje zostavovač/analyzátor dotazov na použitie na strane frontendu;
  • @nestjsx/crud-typeorm — balík pre integráciu s TypeORM, poskytujúci základnú službu TypeOrmCrudService s metódami CRUD pre prácu s entitami v databáze.

V tomto návode budeme potrebovať balíčky hniezdojsx/crud a hniezdojsx/crud-typeorm. Najprv ich položme

yarn add @nestjsx/crud class-transformer class-validator

balíčky trieda-transformátor и trieda-validátor v tejto aplikácii sú potrebné pre deklaratívny popis pravidiel pre transformáciu modelových inštancií a validáciu prichádzajúcich požiadaviek, resp. Tieto balíčky sú od rovnakého autora, takže rozhrania sú podobné.

Priama implementácia CRUD

Ako vzorový model použijeme zoznam používateľov. Používatelia budú mať nasledujúce polia: id, username, displayName, email. id - auto-inkrementačné pole, email и username - jedinečné polia. Je to jednoduché! Zostáva už len zrealizovať náš nápad v podobe aplikácie Nest.
Najprv musíte vytvoriť modul users, ktorý bude zodpovedný za prácu s používateľmi. Použime cli z NestJS a vykonajte príkaz v koreňovom adresári nášho projektu nest g module users.

Nest užívateľov modulu g

dmitrii@dmitrii-HP-ZBook-17-G3:~/projects/nest-rest git:(master*)$ nest g module users
CREATE /src/users/users.module.ts (82 bytes)
UPDATE /src/app.module.ts (312 bytes)

V tomto module pridáme zložku entity, kde budeme mať modely tohto modulu. Predovšetkým sem pridajme súbor user.entity.ts s popisom modelu používateľa:

user.entity.ts

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string;
@Column({unique: true})
email: string;
@Column({unique: true})
username: string;
@Column({nullable: true})
displayName: string;
}

Aby bol tento model „videný“ našou aplikáciou, je potrebné v module UsersModule importovať TypeOrmModule nasledujúci obsah:

users.module.ts

import { Module } from '@nestjs/common';
import { UsersController } from './controllers/users/users.controller';
import { UsersService } from './services/users/users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
controllers: [UsersController],
providers: [UsersService],
imports: [
TypeOrmModule.forFeature([User])
]
})
export class UsersModule {}

To znamená, že tu dovážame TypeOrmModule, kde ako parameter metódy forFeature Uvádzame zoznam modelov súvisiacich s týmto modulom.

Zostáva len vytvoriť zodpovedajúcu entitu v databáze. Na tieto účely slúži migračný mechanizmus. Ak chcete vytvoriť migráciu založenú na zmenách v modeloch, musíte spustiť príkaz npm run migration:generate -- CreateUserTable:

Názov spoileru

$ npm run migration:generate -- CreateUserTable
Migration /home/dmitrii/projects/nest-rest/migrations/1563346135367-CreateUserTable.ts has been generated successfully.
Done in 1.96s.

Migráciu sme nemuseli písať ručne, všetko sa dialo magicky. Nie je to zázrak! To však nie je všetko. Pozrime sa na vytvorený migračný súbor:

1563346135367-CreateUserTable.ts

import {MigrationInterface, QueryRunner} from "typeorm";
export class CreateUserTable1563346816726 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "email" character varying NOT NULL, "username" character varying NOT NULL, "displayName" character varying, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`DROP TABLE "user"`);
}
}

Ako vidíte, automaticky sa vygenerovala nielen metóda spustenia migrácie, ale aj metóda jej vrátenia späť. Fantastické!
Zostáva len spustiť túto migráciu. To sa vykonáva pomocou nasledujúceho príkazu:

npm run migration:run.

To je všetko, teraz sa zmeny schém presunuli do databázy.
Ďalej vytvoríme službu, ktorá bude zodpovedná za prácu s používateľmi a zdedí ju od TypeOrmCrudService. Úložisko entity záujmu musí byť odovzdané parametru nadradeného konštruktora, v našom prípade User Úložisko.

users.service.ts

import { Injectable } from '@nestjs/common';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { User } from '../../entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class UsersService extends TypeOrmCrudService<User>{
constructor(@InjectRepository(User) usersRepository: Repository<User>){
super(usersRepository);
}
}

Túto službu budeme potrebovať v ovládači users. Ak chcete vytvoriť ovládač, zadajte do konzoly nest g controller users/controllers/users

nest g controller users/controllers/users

dmitrii@dmitrii-HP-ZBook-17-G3:~/projects/nest-rest git:(master*)$ nest g controller users/controllers/users
CREATE /src/users/controllers/users/users.controller.spec.ts (486 bytes)
CREATE /src/users/controllers/users/users.controller.ts (99 bytes)
UPDATE /src/users/users.module.ts (188 bytes)

Otvorme tento ovládač a upravme ho, aby sme pridali trochu kúzla hniezdojsx/crud. Za triedu UsersController Pridajme dekoratér takto:

@Crud({
model: {
type: User
}
})

Crud je dekoratér, ktorý k ovládaču pridáva potrebné metódy pre prácu s modelom. Typ modelu je uvedený v poli model.type konfigurácie dekoratérov.
Druhým krokom je implementácia rozhrania CrudController<User>. „Zostavený“ kód ovládača vyzerá takto:

import { Controller } from '@nestjs/common';
import { Crud, CrudController } from '@nestjsx/crud';
import { User } from '../../entities/user.entity';
import { UsersService } from '../../services/users/users.service';
@Crud({
model: {
type: User
}
})
@Controller('users')
export class UsersController implements CrudController<User>{
constructor(public service: UsersService){}
}

A to je všetko! Teraz ovládač podporuje celý súbor operácií s modelom! neveríš mi? Vyskúšajte našu aplikáciu v akcii!

Vytvorenie skriptu dotazu v TestMace

Na testovanie našej služby použijeme IDE na prácu s API TestMace. Prečo práve TestMace? V porovnaní s podobnými produktmi má nasledujúce výhody:

  • výkonná práca s premennými. V súčasnosti existuje niekoľko typov premenných, z ktorých každá hrá špecifickú úlohu: vstavané premenné, dynamické premenné, premenné prostredia. Každá premenná patrí do uzla s podporou mechanizmu dedičnosti;
  • Jednoducho vytvárajte skripty bez programovania. O tom sa bude diskutovať nižšie;
  • ľudsky čitateľný formát, ktorý umožňuje uložiť projekt v systémoch na správu verzií;
  • automatické dopĺňanie, zvýraznenie syntaxe, zvýraznenie premenných hodnôt;
  • Podpora popisu API s možnosťou importu zo Swagger.

Spustíme náš server príkazom npm start a pokúste sa získať prístup k zoznamu používateľov. Zoznam používateľov, súdiac podľa našej konfigurácie ovládača, možno získať z adresy URL localhost:3000/users. Urobme žiadosť na túto adresu URL.
Po spustení TestMace môžete vidieť rozhranie, ako je toto:

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Vľavo hore je strom projektu s koreňovým uzlom projekt. Skúsme vytvoriť prvú požiadavku na získanie zoznamu používateľov. Na to vytvoríme RequestStep uzol Toto sa vykonáva v kontextovej ponuke uzla Projekt Pridať uzol -> RequestStep.

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Do poľa URL prilepte localhost:3000/users a spustite požiadavku. Dostaneme kód 200 s prázdnym poľom v tele odpovede. Je to pochopiteľné, zatiaľ sme nikoho nepridali.
Vytvorme skript, ktorý bude obsahovať nasledujúce kroky:

  1. vytvorenie používateľa;
  2. žiadosť o ID novo vytvoreného používateľa;
  3. vymazanie podľa ID užívateľa vytvoreného v kroku 1.

Tak, poďme. Pre pohodlie vytvorte uzol ako Dosky. V podstate ide len o priečinok, do ktorého uložíme celý skript. Ak chcete vytvoriť uzol Priečinok, vyberte položku Projekt z kontextovej ponuky uzla Pridať uzol -> Priečinok. Zavolajme uzol skontrolovať-vytvoriť. Vo vnútri uzla skontrolovať-vytvoriť Vytvorme našu prvú požiadavku na vytvorenie používateľa. Zavolajme novovytvorený uzol create-user. To znamená, že v súčasnosti bude hierarchia uzlov vyzerať takto:

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Poďme na otvorenú kartu create-user uzol. Zadajte nasledujúce parametre požiadavky:

  • Typ požiadavky - POST
  • URL – localhost:3000/users
  • Telo – JSON s hodnotou {"email": "[email protected]", "displayName": "New user", "username": "user"}

Splňme túto požiadavku. Naša aplikácia hovorí, že záznam bol vytvorený.

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Nuž, overme si túto skutočnosť. Aby bolo možné v nasledujúcich krokoch pracovať s ID vytvoreného užívateľa, musí byť tento parameter uložený. Mechanizmus je na to ako stvorený. dynamické premenné. Na našom príklade sa pozrieme na to, ako s nimi pracovať. Na karte analyzovanej odpovede vyberte položku vedľa uzla ID v kontextovej ponuke Priradiť k premennej. V dialógovom okne musíte nastaviť nasledujúce parametre:

  • Uzol — v ktorom z predkov vytvoriť dynamickú premennú. Poďme si vybrať skontrolovať-vytvoriť
  • Názov premennej — názov tejto premennej. Zavolajme userId.

Proces vytvárania dynamickej premennej vyzerá takto:

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Teraz sa pri každom vykonaní tohto dotazu hodnota dynamickej premennej aktualizuje. A preto dynamické premenné podporujú mechanizmus hierarchického dedenia, premenné userId budú dostupné v potomkoch skontrolovať-vytvoriť uzol akejkoľvek úrovne vnorenia.
Táto premenná sa nám bude hodiť pri ďalšej požiadavke. Konkrétne požiadame o novovytvoreného používateľa. Ako dieťa uzla skontrolovať-vytvoriť vytvoríme požiadavku overiť, či existuje s parametrom url rovný localhost:3000/users/${$dynamicVar.userId}. Zobraziť dizajn ${variable_name} toto je získanie hodnoty premennej. Pretože Máme dynamickú premennú, takže na jej získanie potrebujete prístup k objektu $dynamicVar, teda úplný prístup k dynamickej premennej userId bude vyzerať takto ${$dynamicVar.userId}. Vykonajme požiadavku a uistite sa, že údaje sú požadované správne.
Posledným krokom je požiadať o vymazanie. Potrebujeme ho nielen na kontrolu fungovania vymazania, ale aj takpovediac na upratanie po sebe v databáze, pretože Polia e-mailu a používateľského mena sú jedinečné. Takže v uzle check-create vytvoríme požiadavku na odstránenie používateľa s nasledujúcimi parametrami

  • Typ požiadavky - VYMAZAŤ
  • url - localhost:3000/users/${$dynamicVar.userId}

Poďme spustiť. Čakáme. Tešíme sa z výsledku)

Teraz môžeme celý tento skript spustiť kedykoľvek. Ak chcete spustiť skript, musíte vybrať z kontextového menu skontrolovať-vytvoriť položka uzla beh.

Rýchla tvorba CRUD s nest, @nestjsx/crud a TestMace

Uzly v skripte sa vykonajú jeden po druhom
Tento skript môžete uložiť do svojho projektu spustením Súbor -> Uložiť projekt.

Záver

Všetky funkcie použitých nástrojov sa jednoducho nezmestili do formátu tohto článku. Pokiaľ ide o hlavného vinníka - balík hniezdojsx/crud - nasledujúce témy zostávajú nepokryté:

  • vlastné overovanie a transformácia modelov;
  • výkonný dopytovací jazyk a jeho pohodlné používanie na prednej strane;
  • predefinovanie a pridanie nových metód k regulátorom surovej suroviny;
  • podpora chvastania;
  • správa vyrovnávacej pamäte.

Aj to, čo je popísané v článku, však stačí na pochopenie, že aj taký enterprise framework ako NestJS má nástroje na rýchle prototypovanie aplikácií. A také cool IDE ako TestMace umožňuje udržať dané tempo.

Zdrojový kód tohto článku spolu s projektom TestMace, k dispozícii v úložisku https://github.com/TestMace/nest-rest. Na otvorenie projektu TestMace stačí to urobiť v aplikácii Súbor -> Otvoriť projekt.

Zdroj: hab.com

Pridať komentár