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

tspace-spear

v1.3.0

Published

tspace-spear is a lightweight, high-performance API framework for Node.js that leverages the native HTTP server and supports uWebSockets.js (C++) for maximum speed and efficiency.

Readme

tspace-spear

NPM version NPM downloads

tspace-spear is a lightweight, high-performance API framework for Node.js, built on the native HTTP server with optional support for uWebSockets.js (C++) to achieve maximum speed and efficiency.

It is designed with a strong focus on developer experience and provides end-to-end (E2E) type safety and testing support across the full request lifecycle, from request input to response output (see E2E).


Features

  • ⚡ High-performance core built on native Node.js HTTP
  • 🚀 Optional uWebSockets.js adapter support for ultra-low latency and maximum throughput
  • 🧠 End-to-end E2E type safety across the entire request → response lifecycle
  • 🎮 Built-in support for Controllers and route-based architecture
  • 🏷️ Powerful Decorator system for routes, middleware, validation, and metadata
  • 💉 Built-in constructor-based dependency injection (DI) for Services
  • 📦 DTO (Data Transfer Object) support for structured and type-safe request handling
  • 📂 Built-in File Upload support via useFileUpload() with zero configuration required
  • 🔌 Native WebSocket support for real-time applications and event-driven systems
  • ⚛️ GraphQL support with flexible schema integration and HTTP adapters
  • 🖥️ Built-in Cluster mode support for multi-core scalability and higher throughput
  • 🧪 Built-in testing utilities for E2E validation
  • 🧩 Simple and intuitive developer experience
  • 📘 Auto-generated Swagger documentation via app.useSwagger() with zero manual configuration
  • 🔥 Lightweight and optimized for high-performance APIs and microservices

Install

Install with npm:

npm install tspace-spear --save
npm install tspace-spear -g

Documentation

See the docs directory for full documentation.

Basic Usage

Getting Started

import { Spear } from "tspace-spear";

new Spear()
.get('/' , () => 'Hello world!')
.get('/json' , () => {
  return {
    message : 'Hello world!'
  }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Quick Started

Generate applications, modules, controllers, services, and middleware with the Spear CLI.

# Install CLI globally
npm install -g tspace-spear

# Create a new application structure
spear create new my-app

✔ Successfully created project "my-app"

📦 Project Structure

src/
├── common/
│   └── middlewares/
│       └── log.middleware.ts
│
├── modules/
│   └── cats/
│       ├── cat.controller.ts
│       ├── cat.service.ts
│       └── cat.dto.ts
│
├── client.ts
└── index.ts

🚀 Next Steps

cd my-app

npm run dev

✔ Server is running at:
  http://localhost:8000

✔ Swagger Docs:
  http://localhost:8000/api/docs

✔ Run E2E client:
  ts-node src/client.ts

Adapter

tspace-spear supports multiple server adapters, including the native Node.js HTTP server and uWebSockets.js for high performance.

⚠️ Requirements for uWebSockets.js Node.js 18 or higher is required Installation is done via GitHub (no official npm release)

import { Spear } from "tspace-spear";
import uWS from "uWebSockets.js";

// Install via package.json
// "dependencies": {
//   "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.45.0"
// }

new Spear({ adapter: uWS })
.get("/", () => "Hello world!")
.get("/json", () => {
  return {
    message: "Hello world!",
  };
})
.listen(8000, () =>
  console.log("uWS server is running at http://localhost:8000")
);

Cluster

Cluster mode allows tspace-spear to run multiple worker processes to fully utilize multi-core CPU performance.

import { Spear } from "tspace-spear";
new Spear({
  cluster : 3
})
.get('/' , () => 'Hello world!')
.get('/json' , () => {
  return {
    message : 'Hello world!'
  }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Global Prefix

Global Prefix allows you to define a base path for all routes in your application.

It helps keep your API structured and consistent (e.g. /api, /v1, /app).

const app = new Spear({
  globalPrefix : '/api', // prefix all routes
})
.get('/' , () => 'Hello world!') // http://localhost:8000/api
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/api/cats
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/api/cats/1
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

// http://localhost:8000/api => 'Hello world!'

// Or this

const app = new Spear()
.useGlobalPrefix('api', {
  exclude : [
    {
      path : '/cats/*',
      // methods : '*'
      // methods : ['GET','POST']
    }
  ]
})
.get('/' , () => 'Hello world!') // http://localhost:8000/api
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/cats
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/cats/1
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Logger

The built-in Logger provides request logging for incoming HTTP requests.

It helps you monitor:

Request method Request path Performance tracking Debugging and observability

You can enable a simple logger or configure advanced logging behavior.

const app = new Spear({
  logger :  true
})
// or use this for logging
.useLogger({
    methods     : ['GET','POST'],
    exceptPath  : /\/benchmark(\/|$)|\/favicon\.ico(\/|$)/ // or use Array ['/']
})
.get('/' , () => 'Hello world!')
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Format Response

Provides a consistent response structure system to standardize how responses are handled across your application.

It ensures that:

  • All responses are predictable
  • Errors are properly structured
  • Missing routes are handled cleanly
  • Global error catching is supported

Notfound

The NotFound handler is triggered when no route matches the incoming request.

const app = new Spear()
.get('/' , () => {
  return { 
    message: 'Hello world'
  }
})
.notfound(({ res } : T.Context) => {
  return res.notFound('Not found!')
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
// http://localhost:8000/notfound => { success: false , message : 'Not found!' , statusCode: 404 }

Response

The response system ensures that all returned values are automatically formatted and sent to the client.

You can return:

  • String
  • Object (JSON)
  • Custom response via ctx.res
import { Spear } from "tspace-spear";

const app = new Spear()
.get('/' , () => {
  return { 
    message: 'Hello world'
  }
})
.response((results, statusCode) => {

    if (typeof results === 'string') return results

    if (Array.isArray(results)) {
        return {
          success: statusCode < 400,
          data: results,
          statusCode
        }
    }

    if (typeof results === 'object' && results !== null) {
        return {
          success: statusCode < 400,
          ...results,
          statusCode
        }
    }

    return {
        success: statusCode < 400,
        data: results,
        statusCode
    }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
// http://localhost:8000 => { success: true , message : 'Hello World' , statusCode: 200 }

Catch

The Catch handler is used to handle unexpected runtime errors globally.

It acts as a safety layer to prevent server crashes and standardize error responses.

import { Spear } from "tspace-spear";
import { z } from "zod";
const app = new Spear()
.get('/' , () => {
  throw new Error('Catching failed')
})
.catch((err, { res } : T.Context) => {

  if(err instanceof z.ZodError) {
      return res
      .status(422)
      .json({
          success    : false,
          message: "Validation failed",
          issues    : err?.issues,
          statusCode : 422
      });
  }

  return res
  .status(500)
  .json({
      success    : false,
      message    : err?.message,
      statusCode : 500
  });
}) 
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
// http://localhost:8000 => { success: false , message : 'Catching failed' , statusCode: 500 }

Cors

CORS (Cross-Origin Resource Sharing) controls which origins are allowed to access your API.

It helps secure your server by restricting or allowing cross-domain requests.

const app = new Spear()
.cors({
    origins: [
      /^http:\/\/localhost:\d+$/
    ],
    credentials: true
})
//.cors() allow *
.listen(port , () => console.log(`Server is now allow cors localhost:* `))

Body

Body parsing allows your server to read incoming request payloads (JSON) and access them via ctx.body.

It enables handling requests with structured data.


new Spear()
// enable body payload
.useBodyParser()
.post('/' , ({ body }) =>  {
  return {
    yourBody : body
  }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

File Upload

File upload support allows handling multipart/form-data requests and working with uploaded files via ctx.files.

It provides:

  • Temporary file handling
  • File size limits
  • Manual file movement
  • Auto cleanup option

import { Spear, type T } from 'tspace-spear';
import path from 'path'

new Spear()
// use this for enable file upload
.useFileUpload({
  limit : 1000 * 1000, // limit for file upload 1_000_000 bytes by default Infinity
  tempFileDir : 'temp', // folder temporary directory by default tmp
  removeTempFile : {
    remove : true, // remove temporary files by default false
    ms : 1000 * 60 // remove file temporary after 60 seconds
  }
})
.post('/' , ({ files } : T.Context) => {

  // you can move the file from temporary to other folder
  // for example please validate the your input file
  const file     = files.file[0]
  const folder   = 'uploads'

  await file.write(path.join(path.resolve(),`${folder}/${+new Date()}.${file.extension}`))

  // after writed the file you should remove the temporary file
  await file.remove()

  return {
    files
  }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Cookie

Cookie support allows you to read and manage HTTP cookies from incoming requests via ctx.cookies.

It is useful for:

  • Session handling
  • Authentication
  • User preferences
  • Stateful requests

new Spear()
.useCookiesParser()
.post('/' , ({ cookies }) =>  {
  return {
    yourCookies : cookies
  }
})
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

Middleware

Middleware is a function that runs before the controller handler and is used to:

  • Intercept requests
  • Modify ctx
  • Validate or block execution
  • Handle authentication / logging / transformations
import { type T } from "tspace-spear"
// file cat-middleware.ts
export default (ctx : T.Context, next: T.NextFunction) =>{
  console.log('cat middleware globals');
  return next();
}

import  Spear { Router, type T } from "tspace-spear";
import CatMiddleware from './cat-middleware.ts'

(async () => {
  const port = Number(process.env.PORT ?? 8000)
  const app = new Spear({
    middlewares: [ CatMiddleware ]
    // if you want to import middlewares with a directory can you follow the example
    // middlewares : {
    //   folder : `${__dirname}/middlewares`,
    //   name :  /middleware\.(ts|js)$/i
    // }
  })

  // or add a middleware
  app.use((ctx : T.Context , next : T.NextFunction) => {
    console.log('global middlewares')
    return next()
  })

   app.get('/' ((ctx,next) => {
    console.log('middleware on the crrent route')
    return next()
   }), ({ res } : T.Context) => {
    return res.json({
      message : 'hello world!'
    });
  })

  app.get('/' , ({ res } : T.Context) => {
    return res.json({
      message : 'hello world!'
    });
  })

  app.listen(port , () => console.log(`Server is now listening http://localhost:8000`))

  // localhost:8000

})()

Controller

A Controller is used to group related routes and define request handlers in a structured way.

It helps organize your application into modules (similar to NestJS / Express routers), while keeping a clean and readable API design.

import { 
  Controller , 
  Middleware , 
  Get , 
  Post,
  Patch,
  Put,
  Delete,  
  WriteHeader, 
  Query, 
  Body,
  Params,
  Cookies,
  Files, 
  StatusCode,
  type T
} from 'tspace-spear';

import CatMiddleware from './cat-middleware.ts'

// file cat-controller.ts
@Controller('/cats')
class CatController {
  @Get('/')
  @Middleware(CatMiddleware)
  @Query('test','id')
  @Cookies('name')
  public async index({ query , cookies } : { 
    query : T.Query<{ id : string }>
    cookies : T.Cookies<{ name : string}>
  }) {

    return {
      query,
      cookies
    }
  }

  @Get('/:id')
  @Middleware(CatMiddleware)
  @Params('id')
  public async show({ params } : T.Context) {
    return {
      params
    }
  }

  @Post('/')
  @Middleware(CatMiddleware)
  public async store({ body } : T.Context) {
    return {
      body
    }
  }

  @Put('/:id')
  @Middleware(CatMiddleware)
  public async update({ files } : T.Context) {
    return {
     files
    }
  }

  @Post('/upload')
  @Middleware(CatMiddleware)
  public async upload({ files } : T.Context) {
    return {
     files
    }
  }

  @Delete('/:id')
  @Middleware(CatMiddleware)
  public async destroy({ params } : T.Context) {
    return {
     params
    }
  }
}

import { Spear } , { Router, type T } from "tspace-spear";

import CatController from './cat-controller.ts'

(async () => {

  const app = new Spear({
    controllers: [ CatController ]
    // if you want to import controllers with a directory can you follow the example
    // controllers : {
    //   folder : `${__dirname}/controllers`, // nestjs style `${__dirname}/modules/*`
    //   name :  /controller\.(ts|js)$/i,

    //   *Auto-generate route metadata for type-safe E2E usage, 
    //   *and swagger documentation. By default if use .useSwagger() in app no need to set any description 
    //   preRouteTypes : true 
    // }
  })

  app.useBodyParser()
  app.useCookiesParser()
  app.useFileUpload()

  app.get('/' , ( { res } : T.Context) => {
    return res.json({
      message : 'hello world!'
    });
  })

  app.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

  // localhost:8000/cats 
  // localhost:8000/cats/41

})()

Service

Registers one or more service classes for Dependency Injection.

All registered services will be available in the controller constructor without manual instantiation.

// cat-service.ts
class CatService {
  public index () {
    return [
      {
        id: 1,
        name: 'cat1'
      },
      {
        id: 2,
        name: 'cat2'
      }
    ]
  }
}

// cat-controller.ts
import { 
  Controller, 
  Get
} from 'tspace-spear';
import CatService from './cat-service.ts'

@Service([CatService]) // don't forgot this to send CatService for Dependency Injection(DI)
@Controller('/cats')
class CatController {

  constructor(
    private catService: CatService
  ) {}

  @Get('/')
  public index() {
    return this.catService.index();
  }
}

// app.ts
import { Spear } from "tspace-spear";

(async () => {

  const app = new Spear({
    controllers : {
      folder : `${__dirname}/controllers`,
      name :  /controller\.(ts|js)$/i,
      preRouteTypes : true 
    }
  });
  
  app.useSwagger();

  app.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`));
})()

Exception

Exceptions are used to return HTTP errors from your application.

import {
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  NotFoundException,
  MethodNotAllowedException,
  ConflictException,
  GoneException,
  UnsupportedMediaTypeException,
  UnprocessableEntityException,
  TooManyRequestsException,
  InternalServerErrorException,
  NotImplementedException,
  BadGatewayException,
  ServiceUnavailableException,
  GatewayTimeoutException,
} from 'tspace-spear/exception';

@Controller('/users')
class UserController {
  @Get('/:id')
  public async show({ params }) {

    if (!params.id) {
      throw new BadRequestException('User id is required');
    }

    return {
      id: params.id,
    };
  }
}

Exception

Exceptions are used to return HTTP errors from your application.

import {
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  NotFoundException,
  MethodNotAllowedException,
  ConflictException,
  GoneException,
  UnsupportedMediaTypeException,
  UnprocessableEntityException,
  TooManyRequestsException,
  InternalServerErrorException,
  NotImplementedException,
  BadGatewayException,
  ServiceUnavailableException,
  GatewayTimeoutException,
} from 'tspace-spear/exception';

@Controller('/users')
class UserController {
  @Get('/:id')
  public async show({ params }) {

    if (!params.id) {
      throw new BadRequestException('User id is required');
    }

    return {
      id: params.id,
    };
  }
}

Dto

DTO (Data Transfer Object) is used to validate and transform incoming request data before it reaches your controller logic.

import { 
  Controller , 
  Post,
  Validate,
  ValidateDto,
  createDtoDecorator,
  type T
} from 'tspace-spear';

import z from "zod";

import { 
  IsString, 
  IsInt, 
  validate 
} from "class-validator";

const ValidateDtoCustomBody = (keys: string[]) => {
  return createDtoDecorator((ctx) => {
    const body = ctx.body ?? {};
    const issues: Array<{ path: string; message: string }> = [];

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];

      if (body[key] == null) {
          issues.push({
              path: key,
              message: "Missing field",
          });
      }
    }

    if (issues.length > 0) {
      throw {
        message : "Validation failed",
        issues
      }
    }
  });
}

const ValidateDtoPromiseBody = (keys: string[]) => {
  return createDtoDecorator(async (ctx) => {
      await new Promise(resolve => setTimeout(resolve,500));
      // check in DB or other async operation
      const cats = await catRepository.findMany({ where : { name : ctx.body.name }});

      if(!cats.length) {
       throw new Error('Validation failed in promise!');
      }

  }, (ctx, error) => {
    // you implement your custom error handling for async validation here
    return ctx.res.status(400).json({
      message: error.message || "Validation failed",
      issues: error.issues || [],
    });
  });
}

const catSchema = z.object({
  name: z.string(),
  age: z.number(),
})

class CreateCatDto {
  @IsString()
  name!: string;

  @IsInt()
  age!: number;
}

// file cat-controller.ts
@Controller('/cats')
export class CatController {

  @Post('/')
  // only required validation without type checking
  @Validate(["name", "age"], { required: { allowEmptyString: false, allowNull: false } })
  public async basic(ctx : T.Context<{ body : { name : any , age : any }}>) {
    return {
      body : ctx.body
    }
  }

  @Post('/custom')
  @ValidateDtoCustomBody(["name", "age"])
  public async custom(ctx : T.Context<{ body : { name : string , age : number }}>) {
    return {
      body : ctx.body
    }
  }

  @Post('/promise')
  @ValidateDtoPromiseBody(['name'])
  public async promise(ctx : T.Context<{ body : { name : string }}>) {
    return {
      body : ctx.body
    }
  }

  @Post('/zod')
  @ValidateDto(catSchema, { adaptor : "zod" })
  public async zod(ctx : T.Context<{ body : z.infer<typeof catSchema>}>) {
    return {
      body : ctx.body
    }
  }

  @Post('/cls')
  @ValidateDto(CreateCatDto)
  public async cls(ctx : T.Context<{ body : CreateCatDto }>) {
    return {
      body : ctx.body
    }
  }
}

import { Spear } , { Router, type T } from "tspace-spear";

import CatController from './cat-controller.ts'

(async () => {

  new Spear({
    controllers: [ CatController ]
  })
  .useBodyParser()
  .listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

  // localhost:8000/cats  // basic implete
  // localhost:8000/cats/zod  // zod implete
  // localhost:8000/cats/promise // promise implete

})()

Router

The Router allows you to organize routes into modular groups, making your application more scalable and maintainable.

It supports:

  • Grouped routes
  • Nested route prefixes
  • Reusable router modules
  • Separation of concerns
import { Spear, Router, type T } from "tspace-spear";

const app = new Spear()

const router = new Router()
    
router.groups('/my',(r) => {

  r.get('/cats' , ({ req , res }) => {

      return res.json({
          message : 'Hello, World!'
      })
  })

  return r
})
    
router.get('/cats' , ({ req , res }) => {
  return res.json({
      message : 'Hello, World!'
  })
})

app.useRouter(router)

app.get('/' , ({ res } : T.Context) => {
  return res.json({
    message : 'hello world!'
  });
})

let port = 8000

app.listen(port , () => console.log(`Server is now listening http://localhost:8000`))

// localhost:8000/my/cats
// localhost:8000/cats

Serve Static Files

Serve files directly from a local directory.


const path = require('path');

new Spear({
  logger: true
})
.get('/file/*', (ctx) => {
  // Maps:
  //   /file/example.pdf
  // -> ./example/example.pdf
  const filePath = path.join(
    path.resolve(),
    'example',
    String(ctx.params['*'])
  );

  return ctx.res.serveMedia(filePath);
})
.listen(3000 , ({ port }) =>  {
  console.log(`server listening on : http://localhost:${port}`)
})

Swagger

Provides built-in Swagger support to document your API endpoints.

It allows you to:

  • Describe request parameters (query, body, params)
  • Generate API documentation
  • Improve developer experience
  • Standardize API contracts

// file cat-controller.ts
import { 
  type T,
  Controller, 
  Get , 
  Post,
  Put,
  Patch,
  Delete,
  Swagger
} from 'tspace-spear';

@Controller('/cats')
class CatController {
  @Get('/')
  @Swagger({
    query : {
      id : {
        type : 'integer'
      },
      name :  {
        type : 'string'
      }
    }
  })
  public async index({ query }  : T.Context) {

    return {
      query
    }
  }

  @Get('/:id')
  @Swagger({
    description : '- message',
    query : {
      id : {
        type : 'integer'
      }
    },
    responses : [
      { status : 200 , description : "OK" , example : { id : 'catz' }},
      { status : 400 , description : "Bad request" , example : { id : 'catz' }}
    ]
  })
  public async show({ params } : T.Context) {
    return {
      params
    }
  }

  @Post('/')
  @Swagger({
    bearerToken : true,
    body : {
      description : 'The description !',
      required : true,
      properties : {
        id : {
          type : 'integer',
          example : 1
        },
        name :  {
          type : 'string',
          example : "xxxxx"
        },
        status :  {
            type : 'string',
            example: "active",
            enum: ['active', 'inactive'],
            description: "User status (active = enabled, inactive = disabled)",
            required : true
        },
        roles: {
            type: 'array',
            items : {
                type: 'string',
                example: "roleA",
                enum: ['roleA', 'roleB']
            }
        },
      }
    }
  })
  public async store({ body } : T.Context) {
    return {
      body
    }
  }

  @Put('/:uuid')
  @Swagger({
    bearerToken : true,
    body : {
      description : 'The description !',
      required : true,
      properties : {
        id : {
          type : 'integer',
          example : 1
        },
        name :  {
          type : 'string',
          example : "xxxxx"
        }
      }
    }
  })
  public async updated({ body } : T.Context) {
    return {
      body
    }
  }

  @Patch('/:uuid')
  @Swagger({
    bearerToken : true,
    body : {
      description : 'The description !',
      required : true,
      properties : {
        id : {
          type : 'integer',
          example : 1
        },
        name :  {
          type : 'string',
          example : "xxxxx"
        }
      }
    }
  })
  public async update({ body } : T.Context) {
    return {
      body
    }
  }

  @Delete('/:uuid')
  @Swagger({
    bearerToken : true
  })
  public async delete({ params } : T.Context) {
    return {
      params
    }
  }

  @Post('/upload')
  @Swagger({
    bearerToken : true,
    files : {
      required : true,
      properties : {
        file : {
          type : 'array',
          items: {
            type  :"string",
            format:"binary"
          }
        },
        name : {
          type : 'string'
        }
      }
    }
  })
  public async upload({ body , files } : T.Context) {
    return {
      body,
      files
    }
  }
}

(async () => {

  await new Spear({
    controllers: [ CatController ]
  })
   .get('/' , ({ res } : T.Context) => {
    return res.json({
      message : 'hello world!'
    });
  })
  // .useSwagger() // by default path is "/api/docs"
  .useSwagger({
    path : "/docs",
    servers : [
      { url : "http://localhost:8000" , description : "development"}, 
      { url : "http://localhost:8000" , description : "production"}
    ],
    info : {
      "title" : "Welcome to the the documentation",
      "description" : "This is the documentation"
    }
  })
  .listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))

  // localhost:8000/docs
})()

WebSocket

Provides built-in WebSocket support for real-time communication.

It allows you to:

  • Handle client connections
  • Send/receive messages
  • Build chat systems
  • Manage real-time events
import { Spear } from "tspace-spear";
import fs from 'fs';
import path from 'path';

new Spear()
.get('/',(ctx) => {
  // you can serve a HTML file for testing WebSocket connection
  const htmlWs = fs.readFileSync(path.join(path.resolve(), 'public', 'ws.html'), 'utf-8');
  return ctx.res.html(htmlWs);
})
.ws(() => {
  const clients = new Map<string, any>();
  return {
    connection: (ws) => {
      console.log("connected");
    },

    message: (ws, msg) => {
      const data = JSON.parse(msg.toString());

      if (data.type === "register") {
        ws.userId = data.userId;
        clients.set(data.userId, ws);

        ws.send(JSON.stringify({
          type: "system",
          message: `registered as ${data.userId}`
        }));

        return;
      }

      if (data.type === "chat") {
        const targetWs = clients.get(data.to);

        if (!targetWs) {
          ws.send(JSON.stringify({
            type: "error",
            message: "User not online"
          }));
          return;
        }

        targetWs.send(JSON.stringify({
          type: "chat",
          from: ws.userId,
          text: data.text
        }));

        return;
      }
    },

    close: (ws) => {
      if (ws.userId) {
        clients.delete(ws.userId);
      }
    },
    
    error: (ws, error) => {
      console.error('WebSocket error:', error);
    }
  };
})
.listen(8000 , ({ port , server }) =>  {
    console.log(`server listening on : http://localhost:${port}`)
})

Graphql

GraphQL CRUD Example with graphql-yoga + tspace-spear

This example shows how to build a simple GraphQL CRUD API using graphql-yoga and tspace-spear.

It includes:

  • GraphQL schema setup
  • Query and Mutation examples
  • Create / Read / Update / Delete operations
  • HTTP integration with graphql-yoga

The server uses an in-memory array as a fake database for simplicity.

Features

  • High performance HTTP server with tspace-spear
  • Native GraphQL schema definitions
  • Full CRUD operations
  • Simple and dependency-light setup
  • Works with standard GraphQL clients and tools
npm install graphql graphql-yoga
import { type T, Spear } from "tspace-spear";

import {
  createYoga,
  createSchema,
} from 'graphql-yoga';

type User = {
  id: number;
  name: string;
  email: string;
};

const users: User[] = [
  {
    id: 1,
    name: 'John',
    email: '[email protected]',
  },
];

const yoga = createYoga({
  graphqlEndpoint: '/graphql',

  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type User {
        id: Int!
        name: String!
        email: String!
      }

      input CreateUserInput {
        name: String!
        email: String!
      }

      input UpdateUserInput {
        id: Int!
        name: String
        email: String
      }

      type Query {
        users: [User!]!
        user(id: Int!): User
      }

      type Mutation {
        createUser(input: CreateUserInput!): User!
        updateUser(input: UpdateUserInput!): User!
        removeUser(id: Int!): User!
      }
    `,

    resolvers: {
      Query: {
        users: () => users,

        user: (_root, args) => {
          return users.find(
            (u) => u.id === args.id,
          );
        },
      },

      Mutation: {
        createUser: (_root, args) => {
          const user = {
            id: Date.now(),
            name: args.input.name,
            email: args.input.email,
          };

          users.push(user);

          return user;
        },

        updateUser: (_root, args) => {
          const user = users.find(
            (u) => u.id === args.input.id,
          );

          if (!user) {
            throw new Error(
              'User not found',
            );
          }

          if (args.input.name !== undefined) {
            user.name = args.input.name;
          }

          if (args.input.email !== undefined) {
            user.email = args.input.email;
          }

          return user;
        },

        removeUser: (_root, args) => {
          const index = users.findIndex(
            (u) => u.id === args.id,
          );

          if (index === -1) {
            throw new Error(
              'User not found',
            );
          }

          const deleted = users[index];

          users.splice(index, 1);

          return deleted;
        },
      },
    },
  }),
});

const app = new Spear()
.get('/graphql',({ req , res }) => yoga(req , res))
.post('/graphql',({ req , res }) => yoga(req , res)) 
.listen(4000 , ({ port , server }) =>  {
  console.log(`server listening on : http://localhost:${port}/graphql`)
})

E2E

Provides end-to-end type safety and testing support across the full request lifecycle, from request input to response output. It allows you to:

// file cat-controller.ts
import z from 'zod';
import {
  type T,
  Controller,
  Get,
  Post,
  Put,
  Delete,
  createDtoDecorator
} from "tspace-spear";

const catSchema = z.object({
  id: z.number(),
  name: z.string(),
  age: z.number(),
});

const catSchemaAction = z.object({
  name: z.string(),
  age: z.number(),
});

type Cat = z.infer<typeof catSchema>

let cats: z.infer<typeof catSchema>[] = [
  { id: 1, name: 'cat1', age: 1.6 },
  { id: 2, name: 'cat2', age: 1.8 },
];

const ValidateDtoBody = (schema: z.ZodTypeAny) => {
  return createDtoDecorator((ctx) => {
    const result = schema.parse(ctx.body);
    ctx.body = result as T.Body;
  });
};
@Controller('/cats')
class CatController {

  @Get('/')
  public async index({
    query,
  }: T.Context) {
    return {
      cats,
    };
  }

  @Get('/:id')
  public async show({ res, params }: T.Context<{ params: { id: number } }>) : Promise<{
    cat : Cat
  }> {
    const cat = cats.find((d) => d.id === Number(params.id));

    if(cat == null) {
      throw res.notFound('not found cat')
    }

    return {
      cat
    };
  }

  @Post('/')
  @ValidateDtoBody(catSchemaAction)
  public async create({
    body,
  }: T.Context<{ body: z.infer<typeof catSchemaAction> }>) {

    const cat = {
      id: cats.length + 1,
      ...body
    }

    cats.push(cat);

    return {
      cat,
      message: 'created',
    };
  }

  @Put('/:id')
  @ValidateDtoBody(catSchemaAction.partial())
  public async update({
    res,
    params,
    body,
  }: T.Context<{
    params: { id: number };
    body: Partial<z.infer<typeof catSchemaAction>>;
  }>) {
    const id = Number(params.id);

    const index = cats.findIndex((d) => d.id === id);

    if (index === -1) {
      throw res.notFound('not found cat')
    }

    cats[index] = {
      ...cats[index],
      ...body,
      id
    };

    const cat = cats[index]

    return {
      message: 'updated',
      cat,
    };
  }

  @Delete('/:id')
  public async remove({ res, params }: T.Context<{ params: { id: number } }>) {
    const id = Number(params.id);

    const index = cats.findIndex((d) => d.id === id);

    if (index === -1) {
      throw res.notFound('not found cat')
    }

    cats = cats.filter((d) => d.id !== id);

    return {
      message: 'deleted',
    };
  }
}

export { CatController };
export default CatController;

// file server/app.ts
import Spear from "tspace-spear";
const app = new Spear({
  logger : true,
  controllers: {
      folder : `${__dirname}/controllers/*`,
      name:/controller\.(ts|js)$/i,
      // don't forget to set this option for auto-generate route metadata for type-safe E2E usage, 
      // and swagger documentation. By default if use .useSwagger() in app no need to set any description
      preRouteTypes: true
  }
})

app.useGlobalPrefix('api');
app.useBodyParser();
app.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`));

type AppRouter = typeof app.contract;
export { AppRouter }
export default app;

// file frontend/index.ts
import { AppRouter } from "./server/app";
import { ApiClient } from "tspace-spear/client";

const client: ApiClient<AppRouter> = new ApiClient(
  `http://localhost:8000/api`
);

await client.get("/catsq"); ❌ // Type error: Argument of type '"/catsq"' is not assignable to parameter of type '"/cats" | "/cats/:id" | ... 3 more
const res = await client.get("/cats");
// res.ok -> true or false
// res.status -> number
// res.headers -> Hearders
// res.data -> { cats: [{ id: 1, name: 'cat1', age: 1.6 },{ id: 2, name: 'cat2', age: 1.8 }] }

// Without checking `res.ok`, `res.data` is always typed as `any`.
if(res.ok) {
  res.data.cats = 1 ❌ // Type error: Type 'number' is not assignable to type '{ id: number; name: string; age: number; }[]'
  res.data.cats[0].name = 1 ❌ // Type error: Type 'number' is not assignable to type 'string'
  res.data.cats[0].age = "1.6" ❌ // Type error: Type 'string' is not assignable to type 'number'
}

await client.get("/cats/:id") ❌ // Expected 2 arguments, but got 1.
await client.get("/cats/:id", { params : { id : "1" }}) ❌ 
// The expected type comes from property 'id' which is declared here on type '{ id: number; }'
await client.get("/cats/:id", { params : { id : 1 }}) ✅

await client.post("/cats") ❌ // Expected 2 arguments, but got 1.
await client.post("/cats",{
  body : {}  ❌ // Type '{}' is missing the following properties from type '{ name: string; age: number; }'
});
await client.post("/cats",{
  body : { name : "super cat" } ❌ 
  // Property 'age' is missing in type '{ name: string; }' 
  // but required in type '{ name: string; age: number; }'.
});
await client.post("/cats",{
  body : { name : "super cat" , age : 5 }  ✅
});

await client.put("/cats") ✅

await client.put("/cats",{
  body : {}  ✅
});

await client.put("/cats",{
  body : { name : 1 } ❌ // Type 'number' is not assignable to type 'string'.
});

await client.put("/cats",{
  body : { name : "super cat" } ✅
});

await client.put("/cats",{
  body : { name : "super cat" , age : 5 }  ✅
});