9.0.2 • Published 18 days ago

@furystack/rest-service v9.0.2

Weekly downloads
316
License
GPL-2.0
Repository
github
Last release
18 days ago

rest-service

REST Service (implementation) package for @furystack/rest

Usage

You can start with importing your custom API Endpoint interface (see @furystack/rest) and with the .useRestService<MyApi>(...) injector extensions method. You can define multiple REST services per injector (even on the same port)

Implementing a custom API

Usage example - Authenticated GET, GET Collection and POST APIs for a custom entity that has a physical store and repository set up

import { MyApi, MyEntity } from 'my-common-package'
import {
  createGetCollectionEndpoint,
  createGetEntityEndpoint,
  Authenticate,
  createPostEndpoint,
} from '@furystack/rest-service'


myInjector.useHttpAuthentication().useRestService<MyApi>({
    port: 8080, // The port to listen
    root: '/api', // Routes will be joined on this root path
    cors: { // Enable CORS
      credentials: true, // Enable cookies for CORS
      origins: ['https://my-frontend-1', 'https://my-frontend-2'], // Allowed origins
    },
    // This API should implement *all* methods that are defined in `MyApi`
    api: {
        // Endpoints that can be called with GET Http method
        GET: {
            '/my-entities': Authenticate()(createGetCollectionEndpoint({ model: MyEntity, primaryKey: 'id' })),
            '/my-entities/:id': Authenticate()(createGetEntityEndpoint({ model: MyEntity, primaryKey: 'id' })),
        },
        // Endpoints that can be called with GET Http method
        POST: {
            '/my-entities': Authenticate()(createPostEndpoint({ model: MyEntity, primaryKey: 'id' })),
        },
    },
})

Endpoint generators (based on Repository DataSets)

If you use the underlying layers of FuryStack (PhysicalStore -> Repository) for an entity type, you can easily create some CRUD endpoints for them. These are the followings:

  • createDeleteEndpoint()
  • createGetCollectionEndpoint()
  • createGetEntityEndpoint()
  • createPatchEndpoint()
  • createPostEndpoint()

The endpoints will use the defined Physical Stores for retrieving entities and the Repository for authorization / event subscriptions.

Custom endpoint implementation

If you want to implement an endpoint with custom logic, you can define it in the following way:

import { Injector } from '@furystack/inject'
import { RestApi } from '@furystack/rest'

export type MyCustomRequestAction = {
      /** The request should contain this POST Body structure */
      body: {
        foo: string
        bar: number
      }
      /** Parameter(s) from the URL */
      url: {
        /** This should be also a part of the URL with the `:entityId` syntax */
        entityId: string
      }

      /** The request should contain this query string parameters in the `?foo=asd&bar=2&baz=false` format */
      query: { foo?: string; bar?: number; baz?: boolean }

      /** The request should contain these header values */
      headers: { foo: string; bar: number; baz: boolean }

      /** The endpoint will return the following structure in the response */
      result: {
        success: boolean
      }
    }

/** In a Common module */
export interface MyApiWithCustomEndpoint extends RestApi {
  POST: {
    '/my-custom-request-action/:entityId': MyCustomRequestAction
  }
}

/** In the Backend code */

import { JsonResult } from '@furystack/rest-service'

const i = new Injector()

i.useRestService<MyApiWithCustomEndpoint>({
  port: 8080,
  root: '/mockApi',
  api: {
    POST: {
      '/my-custom-request-action/:entityId': async ({
        getBody,
        getQuery,
        getUrlParams,
        headers,
        injector,
        // request, // This will be the plain IncomingMessage - you can use it for lower level funcionality, e.g. parsing form data
        // response, // This will be the plain ServerResponse - you can use it for lower level functionality, e.g. streaming binaries
      }) => {
        const body = await getBody() // Body type will be resolved
        console.log(body)

        const queryString = getQuery() // Query types will be resolved
        console.log(queryString)

        const { entityId } = getUrlParams()
        console.log('entity id is:', entityId)

        console.log('The headers are:', headers) /** {foo: 'asd', bar: 2, baz: false} */

        const currentUser = await injector.getCurrentUser() // Injector is scoped to the Request
        console.log('The current user is:', currentUser)

        return JsonResult({ success: true }, 200)
      },
    },
  },
})

/** In the Client */

import { createClient } from '@furystack/rest-client-fetch'

const callApi = createClient<MyApiWithCustomEndpoint>({
  endpointUrl: 'https://localhost:8080/mockApi',
})

const getResult = async () =>
  callApi({
    method: 'POST', // This should be the first property in order to continue with IntelliSense
    action: '/my-custom-request-action/:entityId', // The Request Action name - The rest will be resolved from the types
    body: {
      foo: 'asd',
      bar: 42,
    },
    headers: {
      foo: 'asd',
      bar: 2,
      baz: false,
    },
    query: {
      foo: 'asd',
    },
    url: {
      entityId: 'asd-123',
    },
  })

getResult().then((data) => {
  console.log(data.result) // will be { success: true }
  console.log(data.response.status) // will be 200
})

Payload validation

Type-safe APIs does NOT comes with built-in validation by default - but you can use the JSON Schema for full payload validation. The prefferred way is: 1. Create your API interface 1. Create JSON Schemas from the API (The ts-json-schema-generator package is the best solution nowdays, you can check how it works, here) 1. Use the Validate middleware, as shown in the following example:

import schema from './path-to-my/generated-schema.json'
const myValidatedApi = Validate({
  schema,
  schemaName: 'MyCustomRequestAction' // As defined in the example above
})(...myApiImplementation...)

In that way, you will get full validation for all defined endpoint data (header, body, url parameters, query string) with verbose error messages from ajv (see integration tests)

Authentication and HttpUserContext

You can use the build-in authentication that comes with this package. It contains a session (~cookie) based authentication and Basic Auth. You can use it with the .useCommonAuth() injector extension:

myInjector.useCommonAuth({{
    cookieName: 'sessionId', // The session ID will be stored in this cookie
    enableBasicAuth: true, // Enables / disables standard Basic Authentication
    model: ApplicationUserModel, // The custom User model. Should implement `User`
    hashMethod: (plainText) => myHashMethod(plainText), // Method for password hashing
    getSessionStore: (storeManager) => storeManager.getStoreFor(MySessionModel, 'id'), // Callback to retrieve the Session Store
    getUserStore: (storeManager) => storeManager.getStoreFor(ApplicationUserModel, 'id') // Callback to retrieve the User Store
  }).useRestService<MyApi>({...api options})

Built-in actions

The package contains the following built-in actions

  • ErrorAction - for default error handling and dumping errors in the response
  • GetCurrentUser - Returns the current user
  • IsAuthenticated - Returns if a user is logged in
  • Login - Login with a simple username + password combo
  • Logout - Destroys the current session
  • NotFoundAction - The default '404' fallback route
9.0.2

18 days ago

9.0.1

1 month ago

9.0.0

2 months ago

8.0.0

2 months ago

7.0.20

2 months ago

7.0.19

2 months ago

7.0.18

2 months ago

7.0.17

3 months ago

7.0.16

4 months ago

7.0.15

5 months ago

7.0.8

9 months ago

7.0.7

10 months ago

7.0.9

9 months ago

7.0.12

8 months ago

7.0.13

6 months ago

7.0.10

9 months ago

7.0.11

9 months ago

7.0.14

5 months ago

7.0.6

11 months ago

7.0.5

12 months ago

7.0.0

1 year ago

7.0.4

12 months ago

7.0.3

1 year ago

7.0.2

1 year ago

7.0.1

1 year ago

6.2.20

1 year ago

6.2.21

1 year ago

6.2.22

1 year ago

6.2.23

1 year ago

6.2.17

1 year ago

6.2.18

1 year ago

6.2.16

1 year ago

6.2.19

1 year ago

6.2.15

1 year ago

6.2.14

1 year ago

6.2.10

1 year ago

6.2.13

1 year ago

6.2.11

1 year ago

6.2.12

1 year ago

6.2.9

1 year ago

6.2.8

1 year ago

6.2.5

1 year ago

6.2.4

1 year ago

6.2.7

1 year ago

6.2.6

1 year ago

6.2.3

1 year ago

6.2.2

2 years ago

6.2.1

2 years ago

6.2.0

2 years ago

6.1.6

2 years ago

6.1.7

2 years ago

6.1.2

2 years ago

6.1.1

2 years ago

6.1.4

2 years ago

6.1.3

2 years ago

6.1.5

2 years ago

6.1.0

2 years ago

6.0.7

2 years ago

6.0.6

2 years ago

6.0.8

2 years ago

6.0.5

2 years ago

6.0.4

2 years ago

6.0.1

2 years ago

6.0.3

2 years ago

6.0.2

2 years ago

6.0.0

2 years ago

5.0.4

2 years ago

5.0.3

2 years ago

5.0.1

2 years ago

5.0.0

2 years ago

4.1.8

2 years ago

4.1.9

2 years ago

4.1.10

2 years ago

4.1.12

2 years ago

4.1.6

2 years ago

4.1.4

2 years ago

4.1.3

2 years ago

4.1.5

2 years ago

4.1.0

2 years ago

4.1.2

2 years ago

4.1.1

2 years ago

4.0.21

3 years ago

4.0.20

3 years ago

4.0.19

3 years ago

4.0.18

3 years ago

4.0.17

3 years ago

4.0.16

3 years ago

4.0.15

3 years ago

4.0.14

3 years ago

4.0.12

3 years ago

4.0.11

3 years ago

4.0.13

3 years ago

4.0.10

3 years ago

4.0.9

3 years ago

4.0.7

3 years ago

4.0.8

3 years ago

4.0.5

3 years ago

4.0.4

3 years ago

4.0.6

3 years ago

4.0.3

3 years ago

4.0.2

3 years ago

3.2.0

3 years ago

4.0.1

3 years ago

4.0.0

3 years ago

3.1.3

3 years ago

3.1.2

3 years ago

3.1.1

3 years ago

3.1.0

3 years ago

3.0.3

3 years ago

3.0.2

3 years ago

3.0.1

3 years ago

3.0.0

3 years ago

2.3.7

3 years ago

2.3.6

3 years ago

2.3.5

3 years ago

2.3.4

3 years ago

2.3.3

3 years ago

2.3.2

3 years ago

2.3.1

3 years ago

2.3.0

3 years ago

2.2.6

3 years ago

2.2.5

4 years ago

2.2.4

4 years ago

2.2.3

4 years ago

2.2.2

4 years ago

2.2.1

4 years ago

2.2.0

4 years ago

2.1.14

4 years ago

2.1.13

4 years ago

2.1.12

4 years ago

2.1.10

4 years ago

2.1.11

4 years ago

2.1.9

4 years ago

2.1.8

4 years ago

2.1.7

4 years ago

2.1.6

4 years ago

2.1.5

4 years ago

2.1.4

4 years ago

2.1.3

4 years ago

2.1.2

4 years ago

2.1.1

4 years ago

2.1.0

4 years ago

2.0.0

4 years ago

1.2.1

4 years ago

1.2.0

4 years ago

1.1.12

4 years ago

1.1.11

4 years ago

1.1.8

4 years ago

1.1.7

4 years ago

1.1.6

4 years ago

1.1.5

4 years ago

1.1.4

4 years ago

1.1.3

4 years ago

1.1.2

4 years ago

1.1.1

4 years ago

1.1.0

4 years ago

1.0.1

4 years ago