├── .eslintignore ├── .commitlintrc.json ├── .gitignore ├── .husky ├── pre-commit ├── commit-msg └── pre-push ├── .npmignore ├── typedoc.json ├── .prettierrc ├── src ├── index.ts ├── endpoints │ ├── endpoints.test.ts │ └── index.ts ├── utils │ ├── get.ts │ ├── getResource.ts │ ├── generateQueryString.ts │ └── utils.test.ts ├── episode │ ├── index.ts │ └── episode.test.ts ├── location │ ├── index.ts │ └── location.test.ts ├── character │ ├── index.ts │ └── character.test.ts └── interfaces.ts ├── jest.config.js ├── .eslintrc ├── .github └── workflows │ ├── npm-publish.yml │ ├── deploy-docs.yml │ └── test.yml ├── tsconfig.json ├── package.json ├── CHANGELOG.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | coverage 4 | .vscode 5 | dist 6 | .eslintcache 7 | docs -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npm run doctoc 6 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit ${1} 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run test 5 | npm run build 6 | npm run test:size 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .vscode 4 | .eslint* 5 | .prettierrc 6 | .github 7 | jest* 8 | src 9 | typedoc* 10 | docs 11 | tsconfig* 12 | .husky -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["./src/index.ts", "./src/interfaces.ts"], 3 | "excludeExternals": true, 4 | "hideGenerator": true, 5 | "includeVersion": true, 6 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "bracketSpacing": true, 6 | "trailingComma": "all", 7 | "tabWidth": 2, 8 | "printWidth": 120 9 | } 10 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { getCharacters, getCharacter } from './character' 2 | export { getLocations, getLocation } from './location' 3 | export { getEpisodes, getEpisode } from './episode' 4 | export { getEndpoints } from './endpoints' 5 | export * from './interfaces'; 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | coveragePathIgnorePatterns: ['/src/index.ts'], 4 | roots: ['/src'], 5 | testMatch: ['**/?(*.)+(spec|test).ts'], 6 | transform: { 7 | '^.+\\.ts$': 'ts-jest', 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /src/endpoints/endpoints.test.ts: -------------------------------------------------------------------------------- 1 | import { getEndpoints } from '.' 2 | 3 | describe('getEndpoints', () => { 4 | test('returns API endpoints', async () => { 5 | const res = await getEndpoints() 6 | 7 | expect(Object.keys(res.data)).toEqual(['characters', 'locations', 'episodes']) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint", 6 | "prettier" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:prettier/recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "env": { 14 | "node": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/endpoints/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponse, Endpoints } from '../interfaces' 2 | import getResource from '../utils/getResource' 3 | 4 | /** 5 | * Gets a list of available resources.
6 | * https://rickandmortyapi.com/documentation/#rest 7 | */ 8 | export const getEndpoints = (): Promise> => getResource({ endpoint: '', options: {} }) 9 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: NPM publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | - run: npm install 17 | - run: npm run build 18 | 19 | - uses: JS-DevTools/npm-publish@v1 20 | with: 21 | token: ${{ secrets.NPM_TOKEN }} 22 | 23 | -------------------------------------------------------------------------------- /src/utils/get.ts: -------------------------------------------------------------------------------- 1 | const get = async (endpoint: string): Promise<{ data: unknown; status: number; statusMessage: string }> => { 2 | const res = await fetch(`https://rickandmortyapi.com/api/${endpoint}`) 3 | 4 | // response.status >= 200 && response.status < 300 5 | if (res.ok) { 6 | const data = await res.json() 7 | 8 | return { 9 | data, 10 | status: res.status, 11 | statusMessage: res.statusText, 12 | } 13 | } 14 | 15 | return { 16 | data: {}, 17 | status: res.status, 18 | statusMessage: res.statusText, 19 | } 20 | } 21 | 22 | export default get 23 | -------------------------------------------------------------------------------- /src/utils/getResource.ts: -------------------------------------------------------------------------------- 1 | import { CharacterFilter, EpisodeFilter, LocationFilter } from '../interfaces' 2 | import generateQueryString from './generateQueryString' 3 | import get from './get' 4 | 5 | export interface GetResource { 6 | endpoint: 'character' | 'location' | 'episode' | '' 7 | options: number | number[] | CharacterFilter | LocationFilter | EpisodeFilter 8 | isIdRequired?: boolean 9 | } 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 12 | const getResource = async ({ endpoint, options, isIdRequired = false }: GetResource): Promise => { 13 | const qs = generateQueryString(options, isIdRequired) 14 | 15 | return get(`${endpoint}/${qs}`) 16 | } 17 | 18 | export default getResource 19 | -------------------------------------------------------------------------------- /src/episode/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponse, Episode, EpisodeFilter, Info } from '../interfaces' 2 | import getResource from '../utils/getResource' 3 | 4 | const endpoint = 'episode' 5 | 6 | /** 7 | * Gets a collection of Episodes.
8 | * https://rickandmortyapi.com/documentation/#episode 9 | */ 10 | export const getEpisodes = (filters?: EpisodeFilter): Promise>> => 11 | getResource({ endpoint, options: filters ?? {} }) 12 | 13 | /** 14 | * Gets an Episode by `id` or array of `ids`.
15 | * https://rickandmortyapi.com/documentation/#episode 16 | */ 17 | export const getEpisode = ( 18 | id: T, 19 | ): Promise> => 20 | getResource({ endpoint, options: id, isIdRequired: true }) 21 | -------------------------------------------------------------------------------- /src/location/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponse, Info, Location, LocationFilter } from '../interfaces' 2 | import getResource from '../utils/getResource' 3 | 4 | const endpoint = 'location' 5 | 6 | /** 7 | * Gets a collection of Locations.
8 | * https://rickandmortyapi.com/documentation/#location 9 | */ 10 | export const getLocations = (filters?: LocationFilter): Promise>> => 11 | getResource({ endpoint, options: filters ?? {} }) 12 | 13 | /** 14 | * Gets a Location by `id` or array of `ids`.
15 | * https://rickandmortyapi.com/documentation/#location 16 | */ 17 | export const getLocation = ( 18 | id: T, 19 | ): Promise> => 20 | getResource({ endpoint, options: id, isIdRequired: true }) 21 | -------------------------------------------------------------------------------- /src/character/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponse, Character, CharacterFilter, Info } from '../interfaces' 2 | import getResource from '../utils/getResource' 3 | 4 | const endpoint = 'character' 5 | 6 | /** 7 | * Gets a collection of Characters.
8 | * https://rickandmortyapi.com/documentation/#character 9 | */ 10 | export const getCharacters = (filters?: CharacterFilter): Promise>> => 11 | getResource({ endpoint, options: filters ?? {} }) 12 | 13 | /** 14 | * Gets a Character by `id` or array of `ids`.
15 | * https://rickandmortyapi.com/documentation/#character 16 | */ 17 | export const getCharacter = ( 18 | id: T, 19 | ): Promise> => 20 | getResource({ endpoint, options: id, isIdRequired: true }) 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Builds the docs and deploys to GitHub pages 4 | # 5 | # https://github.com/actions/setup-node 6 | # Using https://github.com/marketplace/actions/deploy-to-github-pages 7 | name: Deploy to Github pages 8 | on: 9 | push: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build-and-deploy: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 🛎️ 18 | uses: actions/checkout@v3 19 | 20 | - name: Install and Build 🔧 21 | run: | 22 | npm install 23 | npm run docs 24 | 25 | - name: Deploy docs 🚀 26 | uses: JamesIves/github-pages-deploy-action@releases/v3 27 | with: 28 | BRANCH: gh-pages # The branch the action should deploy to. 29 | FOLDER: docs # The folder the action should deploy. 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "strictFunctionTypes": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "importHelpers": false, 16 | "skipLibCheck": true, 17 | "esModuleInterop": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "allowSyntheticDefaultImports": true, 20 | "experimentalDecorators": true, 21 | "outDir": "./dist", 22 | "rootDir": "./src", 23 | "types": ["node", "jest"], 24 | "lib": ["ES6", "DOM"] 25 | }, 26 | "include": ["src", "types"], 27 | "exclude": ["node_modules", "**/*.test.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Tests 5 | 6 | on: [pull_request] 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [18] 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | 22 | - run: npm ci 23 | 24 | - name: Tests 25 | run: npm run test:coverage 26 | 27 | - name: Build 28 | run: npm run build 29 | 30 | - name: Bundle size 31 | run: npm run test:size 32 | 33 | - name: Coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{ secrets.github_token }} -------------------------------------------------------------------------------- /src/episode/episode.test.ts: -------------------------------------------------------------------------------- 1 | import { getEpisode, getEpisodes } from '.' 2 | 3 | describe('getEpisodes', () => { 4 | test('response schema', async () => { 5 | const res = await getEpisodes() 6 | 7 | expect(res.status).toBeTruthy() 8 | expect(res.statusMessage).toBeTruthy() 9 | expect(res.data.info).toBeTruthy() 10 | expect(res.data.results).toHaveLength(20) 11 | }) 12 | 13 | test('get by filter', async () => { 14 | const res = await getEpisodes({ episode: 'S01E01' }) 15 | 16 | res.data.results?.forEach((item) => { 17 | expect(item.episode).toContain('S01E01') 18 | }) 19 | }) 20 | 21 | test('pagination', async () => { 22 | const res = await getEpisodes({ page: 2 }) 23 | 24 | expect(res.data.info?.prev).toContain('?page=1') 25 | expect(res.data.info?.next).toContain('?page=3') 26 | }) 27 | }) 28 | 29 | describe('getEpisode', () => { 30 | test('get by ID', async () => { 31 | const res = await getEpisode(1) 32 | 33 | expect(res.data.id).toBe(1) 34 | }) 35 | 36 | test('get by IDs', async () => { 37 | const res = await getEpisode([1, 2]) 38 | 39 | expect(res.data[0].id).toBe(1) 40 | expect(res.data[1].id).toBe(2) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/location/location.test.ts: -------------------------------------------------------------------------------- 1 | import { getLocation, getLocations } from '.' 2 | 3 | describe('getLocations', () => { 4 | test('response schema', async () => { 5 | const res = await getLocations() 6 | 7 | expect(res.status).toBeTruthy() 8 | expect(res.statusMessage).toBeTruthy() 9 | expect(res.data.info).toBeTruthy() 10 | expect(res.data.results).toHaveLength(20) 11 | }) 12 | 13 | test('get by filter', async () => { 14 | const res = await getLocations({ dimension: 'C-137' }) 15 | 16 | res.data.results?.forEach((item) => { 17 | expect(item.dimension).toContain('C-137') 18 | }) 19 | }) 20 | 21 | test('pagination', async () => { 22 | const res = await getLocations({ page: 2 }) 23 | 24 | expect(res.data.info?.prev).toContain('?page=1') 25 | expect(res.data.info?.next).toContain('?page=3') 26 | }) 27 | }) 28 | 29 | describe('getLocation', () => { 30 | test('get by ID', async () => { 31 | const res = await getLocation(1) 32 | 33 | expect(res.data.id).toBe(1) 34 | }) 35 | 36 | test('get by IDs', async () => { 37 | const res = await getLocation([1, 2]) 38 | 39 | expect(res.data[0].id).toBe(1) 40 | expect(res.data[1].id).toBe(2) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/character/character.test.ts: -------------------------------------------------------------------------------- 1 | import { getCharacter, getCharacters } from '.' 2 | 3 | describe('getCharacters', () => { 4 | test('response schema', async () => { 5 | const res = await getCharacters() 6 | 7 | expect(res.status).toBeTruthy() 8 | expect(res.statusMessage).toBeTruthy() 9 | expect(res.data.info).toBeTruthy() 10 | expect(res.data.results).toHaveLength(20) 11 | }) 12 | 13 | test('get by filter', async () => { 14 | const res = await getCharacters({ name: 'Rick', status: 'Alive' }) 15 | 16 | res.data.results?.forEach((item) => { 17 | expect(item.name).toContain('Rick') 18 | expect(item.status).toBe('Alive') 19 | }) 20 | }) 21 | 22 | test('pagination', async () => { 23 | const res = await getCharacters({ page: 2 }) 24 | 25 | expect(res.data.info?.prev).toContain('?page=1') 26 | expect(res.data.info?.next).toContain('?page=3') 27 | }) 28 | }) 29 | 30 | describe('getCharacter', () => { 31 | test('get by ID', async () => { 32 | const res = await getCharacter(1) 33 | 34 | expect(res.data.id).toBe(1) 35 | }) 36 | 37 | test('get by IDs', async () => { 38 | const res = await getCharacter([1, 2]) 39 | 40 | expect(res.data[0].id).toBe(1) 41 | expect(res.data[1].id).toBe(2) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /src/utils/generateQueryString.ts: -------------------------------------------------------------------------------- 1 | import { GetResource } from './getResource' 2 | 3 | export const errorMessage = { 4 | required: 'You are using an invalid argument. As an argument use an integer (Id) or an array of integers (Ids).', 5 | optional: 'You are using an invalid argument. As an argument use a filter object or leave it blank.', 6 | } 7 | 8 | const isInteger = (val: unknown) => typeof val === 'number' && Number.isInteger(val) 9 | 10 | export const isArrayOfIntegers = (val: unknown): boolean => Array.isArray(val) && val.every(isInteger) 11 | 12 | const generateQueryString = (query: GetResource['options'], isIdRequired?: boolean): string => { 13 | if (isIdRequired && isInteger(query)) { 14 | return `/${query}` 15 | } 16 | 17 | if (isIdRequired && isArrayOfIntegers(query)) { 18 | const arrayOfIds = query as number[] 19 | 20 | /** 21 | * [0] forces the API to return an empty array. 22 | * This should be addressed in the next API codebase update. 23 | */ 24 | return `/${arrayOfIds.length ? arrayOfIds : '[0]'}` 25 | } 26 | 27 | if (!isIdRequired && typeof query === 'object' && !Array.isArray(query)) { 28 | const params = new URLSearchParams(query as Record).toString() 29 | return `/?${params}` 30 | } 31 | 32 | throw new Error(errorMessage[isIdRequired ? 'required' : 'optional']) 33 | } 34 | 35 | export default generateQueryString 36 | -------------------------------------------------------------------------------- /src/utils/utils.test.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 2 | //@ts-nocheck 3 | import { getCharacter, getCharacters } from '../character' 4 | import generateQueryString, { errorMessage, isArrayOfIntegers } from './generateQueryString' 5 | 6 | describe('generateQueryString', () => { 7 | test('Id or Ids', () => { 8 | const isIdRequired = true 9 | expect(generateQueryString(1, isIdRequired)).toBe('/1') 10 | expect(generateQueryString([], isIdRequired)).toBe('/[0]') 11 | expect(generateQueryString([1, 2, 3], isIdRequired)).toBe('/1,2,3') 12 | }) 13 | 14 | test('filter object', () => { 15 | expect(generateQueryString({ name: 'rick', status: 'Dead' })).toBe('/?name=rick&status=Dead') 16 | }) 17 | 18 | test('error', () => { 19 | expect(() => generateQueryString()).toThrow() 20 | expect(() => generateQueryString('rick')).toThrow() 21 | expect(() => generateQueryString(1.1)).toThrow() 22 | }) 23 | }) 24 | 25 | describe('isArrayOfIntegers', () => { 26 | test('true', () => { 27 | expect(isArrayOfIntegers([1, 2])).toBe(true) 28 | expect(isArrayOfIntegers([1])).toBe(true) 29 | expect(isArrayOfIntegers([])).toBe(true) 30 | }) 31 | 32 | test('false', () => { 33 | expect(isArrayOfIntegers([1, 'b'])).toBe(false) 34 | expect(isArrayOfIntegers([1, 1.2])).toBe(false) 35 | }) 36 | }) 37 | 38 | describe('Errors', () => { 39 | test('Client', () => { 40 | getCharacters(1).catch((error) => expect(error.message).toBe(errorMessage.optional)) 41 | getCharacter({ name: 'rick' }).catch((error) => expect(error.message).toBe(errorMessage.required)) 42 | getCharacter('wubba lubba dub dub').catch((error) => expect(error.message).toBe(errorMessage.required)) 43 | getCharacters('wubba lubba dub dub').catch((error) => expect(error.message).toBe(errorMessage.optional)) 44 | }) 45 | 46 | test('API', async () => { 47 | const res = await getCharacters({ name: 'wubba lubba dub dub' }) 48 | 49 | expect(res.status).toBe(404) 50 | expect(res.statusMessage).toBeTruthy() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rickmortyapi", 3 | "version": "2.3.0", 4 | "author": "Axel Fuhrmann", 5 | "description": "The Rick and Morty API JS client", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "engines": { 10 | "node": ">= 18" 11 | }, 12 | "scripts": { 13 | "dev": "tsc -p tsconfig.json --watch", 14 | "build": "rm -rf dist && tsc -p tsconfig.json --emitDeclarationOnly", 15 | "postbuild": "esbuild src/index.ts --platform=node --target=node18 --sourcemap --minify --bundle --outfile=dist/index.js", 16 | "lint": "eslint src/. --cache --fix", 17 | "test": "jest", 18 | "test:watch": "jest --watch", 19 | "test:coverage": "jest --coverage", 20 | "test:size": "bundlesize", 21 | "docs": "typedoc", 22 | "docs:watch": "npm run docs -- --watch --preserveWatchOutput", 23 | "doctoc": "doctoc README.md", 24 | "release": "standard-version", 25 | "prepare": "husky install" 26 | }, 27 | "devDependencies": { 28 | "@commitlint/cli": "^17.3.0", 29 | "@commitlint/config-conventional": "^17.3.0", 30 | "@types/jest": "^29.2.3", 31 | "@types/node": "^18.11.10", 32 | "@typescript-eslint/eslint-plugin": "^5.45.0", 33 | "@typescript-eslint/parser": "^5.45.0", 34 | "bundlesize": "^0.18.1", 35 | "doctoc": "^2.0.1", 36 | "esbuild": "^0.15.16", 37 | "eslint": "^8.29.0", 38 | "eslint-config-prettier": "^8.3.0", 39 | "eslint-plugin-prettier": "^4.2.1", 40 | "husky": "^7.0.0", 41 | "jest": "^29.3.1", 42 | "prettier": "^2.3.2", 43 | "standard-version": "^9.5.0", 44 | "ts-jest": "^29.0.3", 45 | "typedoc": "^0.23.21", 46 | "typescript": "^4.9.3" 47 | }, 48 | "standard-version": { 49 | "skip": { 50 | "tag": true 51 | } 52 | }, 53 | "bundlesize": [ 54 | { 55 | "path": "./dist/index.js", 56 | "maxSize": "2 kB" 57 | } 58 | ], 59 | "repository": { 60 | "type": "git", 61 | "url": "git+https://github.com/afuh/rick-and-morty-api-node.git" 62 | }, 63 | "keywords": [ 64 | "node", 65 | "rick and morty", 66 | "api" 67 | ], 68 | "bugs": { 69 | "url": "https://github.com/afuh/rick-and-morty-api-node/issues" 70 | }, 71 | "homepage": "https://github.com/afuh/rick-and-morty-api-node#readme" 72 | } 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [2.3.0](https://github.com/afuh/rick-and-morty-api-node/compare/v0.2.2...v2.3.0) (2023-01-15) 6 | 7 | 8 | ### Features 9 | 10 | * re-export interfaces ([50730f5](https://github.com/afuh/rick-and-morty-api-node/pull/32/commits/50730f54feb2898c4ff59760bdd2c8ca370d3398)) 11 | 12 | ## [2.2.0](https://github.com/afuh/rick-and-morty-api-node/compare/v0.2.2...v2.2.0) (2023-01-02) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * location residents type ([0b80dd3](https://github.com/afuh/rick-and-morty-api-node/commit/0b80dd3a0cdf5da06ba3e65c6d2f73b96d67c03b)) 18 | 19 | ## 2.1.1 20 | 21 | ### Bug Fixes 22 | - episode type [67085dc](https://github.com/afuh/rick-and-morty-api-node/commit/67085dc17943b7bd1bd59209d30ddc67c5c9df47) 23 | 24 | ## 2.0.1 25 | 26 | - remove unused package [ff8673f](https://github.com/afuh/rick-and-morty-api-node/commit/ff8673f5f9359a30154950f72425ace9e1102dff) 27 | 28 | ## 2.0.0 29 | 30 | ### Breaking Changes 31 | - Only Node 18 is supported. 32 | - Use `fetch` API ([experimental](https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental) in Node 18) instead of the `https` module. This makes it easier to use in applications running Webpack 5. 33 | 34 | ## 1.0.0 35 | 36 | ### New Features 37 | - The client has been entirely rewritten in Typescript. 38 | - New client API reference documentation: https://javascript.rickandmortyapi.com 39 | - The methods have been redefined to maintain a more consistent data structure. 40 | - New methods: `getCharacters`, `getLocations` and `getEpisodes` 41 | 42 | ### Breaking Changes 43 | - The response schema of all methods has been updated and return the same structure. https://github.com/afuh/rick-and-morty-api-node#response-schema 44 | - Package imports https://github.com/afuh/rick-and-morty-api-node#usge 45 | - `getCharacter`, `getLocation` and `getEpisode` are now used for non-paginated responses. An `Id` or array of `Ids` is now required. https://github.com/afuh/rick-and-morty-api-node#get-by-id 46 | - To continue using pagination or simply require all items of a resource, new methods are now available. https://github.com/afuh/rick-and-morty-api-node#pagination -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface CharacterLocation { 2 | name: string 3 | url: string 4 | } 5 | 6 | export interface ResourceBase { 7 | id: number 8 | name: string 9 | url: string 10 | created: string 11 | } 12 | 13 | export interface Endpoints { 14 | character: string 15 | location: string 16 | episode: string 17 | } 18 | 19 | export interface CharacterFilter { 20 | name?: string 21 | type?: string 22 | species?: string 23 | /** 24 | * 'Dead' | 'Alive' | 'unknown' 25 | */ 26 | status?: string 27 | /** 28 | * 'Female' | 'Male' | 'Genderless' | 'unknown' 29 | */ 30 | gender?: string 31 | page?: number 32 | } 33 | 34 | export interface LocationFilter extends Pick { 35 | dimension?: string 36 | } 37 | 38 | export interface EpisodeFilter extends Pick { 39 | /** 40 | * Filter by the given episode code. 41 | * i.e: `{ episode: "S01E01" }` 42 | */ 43 | episode?: string 44 | } 45 | 46 | export interface Character extends ResourceBase { 47 | status: 'Dead' | 'Alive' | 'unknown' 48 | species: string 49 | type: string 50 | gender: 'Female' | 'Male' | 'Genderless' | 'unknown' 51 | origin: CharacterLocation 52 | location: CharacterLocation 53 | image: string 54 | episode: string[] 55 | } 56 | 57 | export interface Location extends ResourceBase { 58 | type: string 59 | dimension: string 60 | residents: string[] 61 | } 62 | 63 | export interface Episode extends ResourceBase { 64 | air_date: string 65 | episode: string 66 | characters: string[] 67 | } 68 | 69 | export interface ApiResponse { 70 | /** The HTTP status code from the API response */ 71 | status: number 72 | /** The HTTP status message from the API response */ 73 | statusMessage: string 74 | /** The response that was provided by the API */ 75 | data: T 76 | } 77 | 78 | export interface Info { 79 | /** 80 | * The API will automatically paginate the responses. You will receive up to `20` documents per page. 81 | */ 82 | info?: { 83 | /** The length of the response */ 84 | count: number 85 | /** The amount of pages */ 86 | pages: number 87 | /** Link to the next page (if it exists) */ 88 | next: string | null 89 | /** Link to the previous page (if it exists) */ 90 | prev: string | null 91 | } 92 | results?: T 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Tests](https://github.com/afuh/rick-and-morty-api-node/workflows/Tests/badge.svg)](https://github.com/afuh/rick-and-morty-api-node/actions?query=workflow:Tests) 2 | [![Coverage Status](https://img.shields.io/coveralls/github/afuh/rick-and-morty-api-node/master.svg?style=flat-square)](https://coveralls.io/github/afuh/rick-and-morty-api-node?branch=master) 3 | [![npm version](https://img.shields.io/npm/v/rickmortyapi.svg?style=flat-square)](https://badge.fury.io/js/rickmortyapi) 4 | [![npm downloads](https://img.shields.io/npm/dm/rickmortyapi.svg?style=flat-square)](https://npmjs.org/package/rickmortyapi) 5 | [![Twitter Follow](https://img.shields.io/twitter/follow/rickandmortyapi.svg?style=flat-square&label=Follow)](https://twitter.com/rickandmortyapi) 6 | 7 | 8 | # The Rick and Morty API JavaScript client 9 | A zero dependency client for retrieving content from [The Rick and Morty API](https://rickandmortyapi.com). 10 | 11 | 12 | 13 | 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Client Reference](#client-reference) 17 | - [Response schema](#response-schema) 18 | - [Examples](#examples) 19 | - [Get by Id](#get-by-id) 20 | - [Get by Ids](#get-by-ids) 21 | - [Get all](#get-all) 22 | - [Filter](#filter) 23 | - [Pagination](#pagination) 24 | - [Get endpoints](#get-endpoints) 25 | - [Support](#support) 26 | 27 | 28 | 29 | 30 | ## Installation 31 | *Node 18 or above is required.* 32 | 33 | `npm i rickmortyapi` or `yarn add rickmortyapi` 34 | 35 | ## Usage 36 | ```js 37 | import { getCharacter } from 'rickmortyapi' 38 | // or 39 | import * as shlaami from 'rickmortyapi' // shlaami.getCharacter() 40 | // or 41 | const plumbus = require('rickmortyapi') 42 | // a Plumbus will provide you with a lifetime of better living and happiness. 43 | ``` 44 | 45 | ## Client Reference 46 | - [Methods](https://javascript.rickandmortyapi.com/modules/index.html) 47 | - [Interfaces](https://javascript.rickandmortyapi.com/modules/interfaces.html) 48 | 49 | ## Response schema 50 | The response for each method contains the following structure. 51 | 52 | ```js 53 | { 54 | // The HTTP status code from the API response 55 | data: {}, 56 | 57 | // The HTTP status message from the API response 58 | status: 200, 59 | 60 | // The response that was provided by the API 61 | statusMessage: 'OK', 62 | } 63 | ``` 64 | 65 | ## Examples 66 | All methods return a `promise`. 67 | 68 | ### Get by Id 69 | ```js 70 | const rick = await getCharacter(1) 71 | const earth = await getLocation(1) 72 | const episodeOne = await getEpisode(1) 73 | ``` 74 | 75 | ### Get by Ids 76 | ```js 77 | const theSmiths = await getCharacter([ 2, 3, 4, 5 ]) 78 | const [ earth, citadel ] = await getLocation([ 1 , 3 ]) 79 | const s01 = await getEpisode(Array.from({ length: 11 }, (v, i) => i + 1)) 80 | ``` 81 | 82 | ### Get all 83 | ```js 84 | const characters = await getCharacters() 85 | const locations = await getLocations() 86 | const episodes = await getEpisodes() 87 | ``` 88 | 89 | ### Filter 90 | To know more about filtering check the [API documentation](https://rickandmortyapi.com/documentation/#filter-characters) or the client [reference](https://javascript.rickandmortyapi.com/interfaces/interfaces.CharacterFilter.html). 91 | 92 | ```js 93 | const aliveRicks = await getCharacters({ 94 | name: 'rick', 95 | status: 'alive' 96 | }) 97 | 98 | const planets = await getLocations({ 99 | type: 'planet', 100 | page: 2 101 | }) 102 | 103 | const seasonOne = await getEpisodes({ 104 | episode: 's01' 105 | }) 106 | ``` 107 | 108 | ### Pagination 109 | In methods that return a paginated response (`getCharacters`, `getLocations` and `getEpisodes`), you can use a [`page`](https://rickandmortyapi.com/documentation/#info-and-pagination) property to access different pages. 110 | 111 | ```js 112 | const moreCharacters = await getCharacters({ page: 2 }) 113 | ``` 114 | 115 | ### Get endpoints 116 | `getEndpoints()`: This method will response with the available resouces, you can use it to ping the server status. 117 | 118 | ## Support 119 | [Help to maintain The Rick and Morty API's infrastructure](https://rickandmortyapi.com/help-us). 120 | 121 | If you want to know more about The Rick and Morty API click [here](https://rickandmortyapi.com/about). --------------------------------------------------------------------------------