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

@forgrit/deploy-railway

v0.1.0

Published

Framework-agnostic Railway deployment adapter implementing @forgrit/deploy-core. Hand-rolled GraphQL adapter for Railway's projectCreate/serviceCreate/deploymentCreate API. Bring your own NestJS/Express/orchestrator.

Downloads

172

Readme

@forgrit/deploy-railway

Framework-agnostic Railway deployment adapter implementing IDeploymentProvider from @forgrit/deploy-core. Hand-rolled GraphQL — no graphql-request / @apollo/client runtime dependency.

Status: early-access (v0.x). v0.1.0 covers the standard Railway 3-mutation deploy sequence (projectCreateserviceCreatedeploymentCreate) + deployment status query + projectDelete teardown. Composite deploymentId format projectId:serviceId:deploymentId for full hierarchy tracking.

Install

npm install @forgrit/deploy-railway @forgrit/deploy-core
# or
pnpm add @forgrit/deploy-railway @forgrit/deploy-core

Quick example

import { RailwayProvider } from '@forgrit/deploy-railway';

const provider = new RailwayProvider({
  token: process.env.RAILWAY_TOKEN!,
});

const result = await provider.deploy({
  jobId: 'job-42',
  workspacePath: '/path/to/workspace', // not directly uploaded; Railway pulls from Git
  projectName: 'my-app',
});
console.log(result.deploymentId); // "projectId:serviceId:deploymentId"

// Poll until ready.
let status = await provider.getStatus(result.deploymentId);
while (status.status === 'queued' || status.status === 'building') {
  await new Promise((r) => setTimeout(r, 2000));
  status = await provider.getStatus(result.deploymentId);
}

// Teardown — deletes the whole Railway project.
await provider.teardown(result.deploymentId);

NestJS / orchestrator integration

This package intentionally has no NestJS, Prisma, or app-framework imports. Wire it into your orchestrator by passing config + logger hooks as constructor options.

import { Injectable, BadRequestException } from '@nestjs/common';
import {
  RailwayCompositeIdError,
  RailwayProvider as CoreRailwayProvider,
} from '@forgrit/deploy-railway';

@Injectable()
export class MyRailwayProvider {
  private readonly delegate: CoreRailwayProvider;

  constructor(private readonly logger: MyLoggerService) {
    this.delegate = new CoreRailwayProvider({
      token: () => process.env.RAILWAY_TOKEN, // lazy
      logger: this.logger,
    });
  }

  async getStatus(deploymentId: string) {
    try {
      return await this.delegate.getStatus(deploymentId);
    } catch (err) {
      if (err instanceof RailwayCompositeIdError) {
        // Re-throw as your framework's 400 type if needed.
        throw new BadRequestException(err.message);
      }
      throw err;
    }
  }
}

API surface

| Export | Kind | Purpose | | ------------------------- | ----- | ------------------------------------------------------------------------------------------------- | | RailwayProvider | class | The adapter — implements IDeploymentProvider | | RailwayProviderOptions | type | Constructor argument shape | | RailwayLogger | type | Optional log/warn/error hooks (all 3 methods optional) | | RailwayDeployEventStore | type | Optional non-blocking event persistence (findLatest + recordTransition) | | RailwayFetch | type | Injectable fetch shape (for testing without monkey-patching globals) | | RailwayDeployEvent | type | Minimal event row shape (just { toState }) | | RailwayDeployStatus | type | Alias for DeployStatusResult['status'] from @forgrit/deploy-core | | RailwayCompositeId | type | Parsed { projectId, serviceId, deploymentId } | | RailwayCompositeIdError | class | Thrown by parseRailwayCompositeId() for malformed inputs | | parseRailwayCompositeId | fn | Pure helper — exported for diagnostics + framework-specific error translation | | mapRailwayStatus | fn | Pure helper — Railway status → deploy-core status union (9-state mapping including cancelled) |

Behavior

| Method | What it does | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | deploy(params) | 3 sequential GraphQL mutations: projectCreateserviceCreatedeploymentCreate. Returns { deploymentId: "projectId:serviceId:deploymentId", deployUrl, provider: 'railway', status: 'queued' }. Optionally records the initial queued transition (deduped if a prior event exists for deploymentId). | | getStatus(deploymentId) | Parses the composite ID, runs deployment(id: $id) query against Railway. Maps status to the deploy-core status union. Optionally records state-change transitions; same-state polls are skipped. Backward-compat: also accepts raw deployment IDs (strings without :). | | teardown(deploymentId) | Parses the composite ID, runs projectDelete(id: $id) mutation. Backward-compat: also accepts raw project IDs. |

Status mapping (9 states):

| Railway status | Mapped status | | ----------------------- | ------------- | | WAITING, QUEUED | queued | | BUILDING, DEPLOYING | building | | SUCCESS, RUNNING | ready | | CRASHED, FAILED | error | | REMOVED, CANCELLED | cancelled | | <unknown> | error |

Composite deploymentId format

Railway's API hierarchy requires tracking three IDs (project, service, deployment). Rather than forcing callers to manage 3 separate strings, RailwayProvider returns a single colon-separated composite:

projectId:serviceId:deploymentId

getStatus() parses out the deploymentId part for the deployment query. teardown() parses out the projectId part for the projectDelete mutation. Both methods accept either the composite form OR a bare ID string (backward-compat with legacy callers).

Malformed composite IDs (wrong part count, empty parts) throw RailwayCompositeIdError. NestJS / Express apps can catch this and re-throw as BadRequestException (or equivalent 400-class error) to keep the public API contract clean.

GraphQL transport

Railway's API is GraphQL-based. v0.1.0 hand-rolls the request via globalThis.fetch (or your injected fetch) — no graphql-request / @apollo/client / urql runtime dependency. Two-line POST helper does the job; switching to a real GraphQL client is a future v0.x decision if/when query/mutation complexity grows.

Errors are detected via TWO paths:

  1. Non-2xx HTTP responses → throws Railway API error: <status> <body>
  2. GraphQL-level errors array → throws Railway GraphQL error: <first message>

Both paths surface enough information for retries + audit logging without leaking GraphQL-internal noise.

Engines

  • Node >= 20 (uses native globalThis.fetch). Tests can pass an injected fetch to run on older Node versions.

License

MIT. See LICENSE.

Issues: https://github.com/forgrit-ai/forgrit/issues

Related packages

  • @forgrit/deploy-core — the provider contract this package implements
  • @forgrit/deploy-vercel — sibling compute-provider adapter for Vercel
  • @forgrit/deploy-neon (plan #23b, in flight) — Neon Postgres provisioner

See plan #23c for design rationale.