npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sotatech/nest-quickfix

v0.11.0

Published

A powerful NestJS implementation of the FIX (Financial Information eXchange) protocol. Provides high-performance, reliable messaging for financial trading applications with built-in session management, message validation, and recovery mechanisms.

Readme

Nest QuickFIX

npm version license

A NestJS library that embeds a FIX (Financial Information eXchange) protocol engine directly into your application. Run a FIX acceptor (server) or initiator (client) over raw TCP, handle incoming messages with decorators, and broadcast outbound messages to groups of sessions — all with familiar NestJS patterns.

Features

  • FIX acceptor & initiator — TCP server accepting counterparties, or TCP client connecting out to a broker/exchange, from the same module
  • Session management — logon/logout handshake, heartbeat supervision, sequence validation, resend/replay and gap-fill recovery
  • Decorator-based handlers@OnLogon(), @OnFixMessage(MsgType.NewOrderSingle), ... on any controller method
  • Room-based broadcasting — group sessions into rooms and send a message to all of them with fixService.to(room).send(msg)
  • FIX field dictionary — 680+ typed field-tag and value enums (Fields, MsgType, Side, OrdType, TimeInForce, ...) with full tag/name coverage of FIX 4.4
  • Byte-correct message framing — ASCII BodyLength (9) framing, configured width, checksum and maximum-frame validation across fragmented/coalesced TCP chunks
  • Bounded reconnection — serialized reconnect attempts, Logon timeout and stale-socket protection for the initiator
  • Sync & async configurationFIXModule.register() and FIXModule.registerAsync()

Requirements

  • Node.js 18+ (20+ recommended)
  • NestJS 11 (@nestjs/common and @nestjs/core are peer dependencies)

Installation

# Using npm
npm install @sotatech/nest-quickfix

# Using yarn
yarn add @sotatech/nest-quickfix

# Using pnpm
pnpm add @sotatech/nest-quickfix

@nestjs/common, @nestjs/core, reflect-metadata and rxjs must be present in the host application (they already are in any NestJS app).

Quick Start

1. Register the module

Acceptor (listen for incoming FIX connections)

import { Module } from '@nestjs/common';
import { FIXModule, Fields, Message } from '@sotatech/nest-quickfix';

@Module({
  imports: [
    FIXModule.register({
      config: {
        application: {
          name: 'MyFixGateway',
          dictionary: 'FIX44',
          protocol: 'ascii',
          reconnectSeconds: 30,
          tcp: {
            host: '0.0.0.0',
            port: 9876,
          },
          type: 'acceptor',
        },
        BeginString: 'FIX.4.4',
        BodyLengthChars: 10,
        EncryptMethod: 0,
        HeartBtInt: 30,
        ResetSeqNumFlag: true,
        SenderCompID: '*', // '*' accepts any sender
        TargetCompID: 'BROKER',
      },
      auth: {
        // Receives the raw Logon message — extract any field you need
        validateCredentials: async (message: Message) => {
          const username = message.getField(Fields.Username);
          const password = message.getField(Fields.Password);
          return username === 'username' && password === 'password';
        },
        // Optional: allow-list per Account (tag 1) from the Logon message
        getAllowedSenderCompIds: async (account: string) => [
          'CLIENT1',
          'CLIENT2',
        ],
      },
      session: {
        maxSessions: 0, // 0 = unlimited
        messageStoreCapacity: 10_000,
        maxMessageBytes: 1_048_576,
        logonTimeoutMs: 10_000,
      },
    }),
  ],
})
export class AppModule {}

Initiator (connect out to an acceptor)

FIXModule.register({
  config: {
    application: {
      name: 'MyFixClient',
      dictionary: 'FIX44',
      protocol: 'ascii',
      reconnectSeconds: 30,
      tcp: {
        host: 'broker.example.com',
        port: 9876,
      },
      type: 'initiator',
    },
    BeginString: 'FIX.4.4',
    BodyLengthChars: 10,
    EncryptMethod: 0,
    HeartBtInt: 30,
    ResetSeqNumFlag: true,
    SenderCompID: 'CLIENT1',
    TargetCompID: 'BROKER',
    // Optional credentials — sent as tags 553/554 in the Logon message
    Username: 'username',
    Password: 'password',
    reconnect: {
      enabled: true,
      interval: 5000, // ms between attempts
      maxAttempts: 5,
    },
  },
});

Async configuration

FIXModule.registerAsync({
  inject: [ConfigService],
  useFactory: async (config: ConfigService) => ({
    config: config.get('fix'),
    auth: {/* ... */},
  }),
});

2. Handle FIX events in a controller

import { Controller } from '@nestjs/common';
import {
  Message,
  Field,
  Fields,
  MsgType,
  Session,
  FixService,
  OnLogon,
  OnLogout,
  OnConnected,
  OnDisconnected,
  OnFixMessage,
} from '@sotatech/nest-quickfix';

@Controller()
export class TradingController {
  constructor(private readonly fixService: FixService) {}

  @OnLogon()
  async onLogon(session: Session, message: Message) {
    // Group the session into a room for targeted broadcasting
    session.join('CLIENTS');

    // Announce trading session status right after logon
    const status = new Message(
      new Field(Fields.MsgType, MsgType.TradingSessionStatus),
      new Field(Fields.TradingSessionID, '1'),
    );
    await this.fixService.to(session.getSessionId()).send(status);
  }

  @OnFixMessage(MsgType.NewOrderSingle)
  async onNewOrder(session: Session, message: Message) {
    const clOrdId = message.getField(Fields.ClOrdID);
    const symbol = message.getField(Fields.Symbol);
    const side = message.getField(Fields.Side);
    const qty = message.getField(Fields.OrderQty);
    // ... route the order to your matching logic
  }

  @OnFixMessage() // no filter: receives every inbound application message
  async onAnyMessage(session: Session, message: Message) {
    console.log('IN', message.toFieldNameObject());
  }

  @OnLogout()
  async onLogout(session: Session, message: Message) {
    console.log(`Logout: ${session.getSessionId()}`);
  }

  @OnConnected()
  async onConnected(session: Session) {}

  @OnDisconnected()
  async onDisconnected(session: Session) {}
}

3. Send messages from anywhere via FixService

import { Injectable } from '@nestjs/common';
import {
  FixService,
  Message,
  Field,
  Fields,
  MsgType,
  Side,
  OrdType,
  TimeInForce,
  RejectMessage,
} from '@sotatech/nest-quickfix';

@Injectable()
export class OrderGateway {
  constructor(private readonly fixService: FixService) {}

  async sendOrder() {
    const order = new Message(
      new Field(Fields.MsgType, MsgType.NewOrderSingle),
      new Field(Fields.ClOrdID, 'ORDER-123'),
      new Field(Fields.Symbol, 'AAPL'),
      new Field(Fields.Side, Side.Buy),
      new Field(Fields.OrderQty, 100),
      new Field(Fields.Price, 150.5),
      new Field(Fields.OrdType, OrdType.Limit),
      new Field(Fields.TimeInForce, TimeInForce.Day),
    );

    // Target can be a room name or a session id ("SENDER->TARGET")
    await this.fixService.to('CLIENTS').send(order);
  }

  async reject(refSeqNum: number, reason: string) {
    const reject = new RejectMessage(refSeqNum, reason, MsgType.NewOrderSingle);
    await this.fixService.to('CLIENT1->BROKER').send(reject);
  }
}

You never set BeginString, SenderCompID, TargetCompID, MsgSeqNum, SendingTime, BodyLength or CheckSum yourself — the library fills them in per session.

Configuration reference

FIXModuleOptions

| Option | Type | Description | | ----------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------- | | config | FIXConfig | Connection and session configuration (below) | | auth | object | Optional. Authentication hooks for the acceptor | | auth.validateCredentials | (message: Message) => boolean \| Promise<boolean> | Receives the raw Logon message; return false to reject | | auth.getAllowedSenderCompIds (optional) | (account: string) => string[] \| Promise<string[]> | Allowed SenderCompIDs for the given Account (tag 1) | | session.maxSessions | number | Optional. Max concurrent sessions, 0 = unlimited | | session.messageStoreCapacity | number | Optional. Outbound entries retained per session for replay; default 10_000 | | session.maxMessageBytes | number | Optional. Maximum inbound frame/buffer size; default 1_048_576 (1 MiB) | | session.logonTimeoutMs | number | Optional. Initiator timeout waiting for peer Logon; default 10_000 ms |

FIXConfig

| Field | Type | Required | Description | | ------------------------------------------------ | --------------------------- | -------- | ----------------------------------------------------------------------------------- | | application.type | 'acceptor' \| 'initiator' | yes | Run as server or client | | application.name | string | yes | Application name | | application.dictionary | string | yes | FIX dictionary, e.g. 'FIX44' | | application.protocol | 'ascii' | yes | Wire encoding | | application.reconnectSeconds | number | yes | Seconds between reconnects | | application.tcp.host | string | yes | Bind host (acceptor) or remote host (initiator) | | application.tcp.port | number | yes | TCP port | | BeginString | string | yes | FIX version, e.g. 'FIX.4.4' | | SenderCompID | string \| '*' | yes | Your comp ID; '*' = accept any (acceptor) | | TargetCompID | string \| '*' | yes | Counterparty comp ID | | HeartBtInt | number | yes | Heartbeat interval, seconds | | EncryptMethod | number | yes | 0 = none | | ResetSeqNumFlag | boolean | yes | Reset sequence numbers on logon | | BodyLengthChars | number | yes | Exact tag 9 width used by the engine; serialization fails if the value does not fit | | Username / Password | string | no | Credentials (acceptor-side validation reference) | | TargetSubID | string | no | Target sub ID | | LastSentSeqNum / LastReceivedSeqNum | number | no | Restore sequence numbers | | reconnect.enabled / interval / maxAttempts | | no | Initiator reconnect policy |

Decorators

| Decorator | Handler signature | Fires when | | ------------------------- | -------------------- | ------------------------------------------------- | | @OnLogon() | (session, message) | Logon handshake completed | | @OnLogout() | (session, message) | Logout received | | @OnConnected() | (session) | TCP connection established | | @OnDisconnected() | (session) | TCP connection closed | | @OnFixMessage(msgType?) | (session, message) | Inbound message; optionally filtered by MsgType |

Handlers can be placed on methods of any @Controller() or @Injectable() provider discovered by NestJS.

Sessions and rooms

Every connection is represented by a Session:

session.getSessionId(); // "SENDER->TARGET"
session.join('roomA'); // join a room
session.leave('roomA'); // leave a room
session.getRooms(); // list joined rooms
session.getConfig(); // SessionConfig
await session.sendMessage(message); // send to this session only

FixService.to(target) accepts either a room name (broadcast to all sessions in the room) or a session id (send to one session). If no live session exists, send() rejects with the exported FixTargetNotFoundError. Broadcasts use an independent message clone per recipient; if any write fails, the returned promise rejects.

Sequence counters start at LastSentSeqNum + 1 and LastReceivedSeqNum + 1. A peer gap is buffered and requested with ResendRequest; application messages are replayed with PossDupFlag/OrigSendingTime, while admin ranges are represented by SequenceReset-GapFill. The store is in memory and bounded by messageStoreCapacity. If a requested application sequence has already been evicted, the engine sends Logout and closes the session instead of silently gap-filling business data. Restarting the process does not restore stored messages.

Message API

const msg = new Message(
  new Field(Fields.MsgType, MsgType.ExecutionReport),
  new Field(Fields.OrderID, 'X1'),
);

msg.getField(Fields.OrderID); // typed getter
msg.setField(Fields.Price, 42.5);
msg.hasField(Fields.Price); // true
msg.toString(); // minimal-width BodyLength + CheckSum computed
msg.toString({ bodyLengthChars: 10 }); // exact tag 9 width used by the engine
msg.clone(); // deep copy of the field map
msg.createReverse(); // copy with SenderCompID/TargetCompID swapped
msg.toJSON(); // { fields: { 35: '8', ... } }
msg.toFieldNameObject(); // { MsgType: '8', OrderID: 'X1', ... }
Message.fromJSON(json); // rebuild from JSON

Fields is the tag-number enum (Fields.ClOrdID === 11). Value enums (MsgType, Side, OrdType, TimeInForce, and 600+ more) type the field values.

Events

FIXAcceptor, FIXInitiator and Session are EventEmitters:

import {
  SessionEvents,
  InitiatorEvents,
  AcceptorEvents,
} from '@sotatech/nest-quickfix';

// e.g. injected FIXInitiator
initiator.on(InitiatorEvents.LOGGED_ON, () => {
  /* ... */
});
initiator.on(InitiatorEvents.RECONNECT_FAILED, ({ attempt, error }) => {
  /* ... */
});
session.on(SessionEvents.MESSAGE_IN, (msg) => {
  /* ... */
});

Architecture

flowchart LR
  subgraph External["External Counterparties"]
    CP["FIX Client / Broker"]
  end

  subgraph Lib["@sotatech/nest-quickfix"]
    Acceptor["FIXAcceptor - TCP server"]
    Initiator["FIXInitiator - TCP client"]
    SessionMgr["SessionManager"]
    SessionObj["Session - heartbeat, seq nums"]
    Parser["FIXMessageParser - SOH framing, checksum"]
    Explorer["FixMetadataExplorer - decorator discovery"]
    Rooms["RoomManager"]
  end

  subgraph App["Your Application"]
    Ctrl["Controller handlers"]
    Svc["Services via FixService"]
  end

  CP -->|"TCP / FIX over SOH"| Acceptor
  Initiator -->|"TCP / FIX over SOH"| CP
  Acceptor --> SessionMgr
  SessionMgr --> SessionObj
  SessionObj --> Parser
  Parser --> Explorer
  Explorer --> Ctrl
  Svc --> Rooms
  Rooms --> SessionObj

Flow inbound: TCP bytes → FIXMessageParser (frame by tag 9 byte length, validate field order + checksum) → Session (sequence validation/recovery, admin messages) → your decorated handlers. Waiting-gap and duplicate messages are not emitted to application handlers. Flow outbound: FixServiceRoomManagerSession → required header fields + in-memory replay store + checksum → socket.

Sample application

A complete working acceptor lives in samples/nest-quickfix-sample. It consumes the library via a local file: link:

cd samples/nest-quickfix-sample
pnpm install
pnpm start:dev
# FIX acceptor now listens on localhost:9876

Point any FIX 4.4 client (e.g. QuickFIX/J's Banzai, or a raw telnet-style script) at it to see logon, heartbeat and message handling in action.

Development

pnpm install     # install dependencies
pnpm build       # compile to dist/
pnpm lint        # eslint (flat config)
pnpm format      # prettier
pnpm test        # jest unit tests
pnpm test:cov    # coverage with enforced thresholds

The package is published with scripts/publish.sh (builds and publishes dist/ with public access).

Roadmap

Potential future extensions:

  • Persistent session store (Redis) for sequence numbers and message recovery
  • TLS/SSL transport
  • FIX 5.0 session layer (the field dictionary already covers 5.0 tags)

Contributing

  1. Fork and clone the repo
  2. pnpm install, create a feature branch
  3. Make sure pnpm build and pnpm lint pass
  4. Open a pull request

Please report issues at github.com/sotaaaaa/nest-quickfix/issues.

License

MIT