{"id":18002322,"url":"https://github.com/jaeyson/ex_typesense","last_synced_at":"2026-04-14T01:05:33.798Z","repository":{"id":65378467,"uuid":"577086718","full_name":"jaeyson/ex_typesense","owner":"jaeyson","description":"Typesense client for Elixir with support for importing your Ecto schemas.","archived":false,"fork":false,"pushed_at":"2026-04-12T03:35:14.000Z","size":477,"stargazers_count":46,"open_issues_count":3,"forks_count":12,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-12T04:22:15.915Z","etag":null,"topics":["ecto","ecto-schemas","elixir","hacktoberfest","typesense"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/ex_typesense","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaeyson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-12-11T23:13:38.000Z","updated_at":"2026-04-12T03:35:19.000Z","dependencies_parsed_at":"2024-03-23T17:22:41.396Z","dependency_job_id":"ee0c0bfc-734f-424b-bebc-336796931475","html_url":"https://github.com/jaeyson/ex_typesense","commit_stats":{"total_commits":9,"total_committers":1,"mean_commits":9.0,"dds":0.0,"last_synced_commit":"3dd889b5e8964685ce595fd416bf0106aae02c9c"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/jaeyson/ex_typesense","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaeyson%2Fex_typesense","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaeyson%2Fex_typesense/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaeyson%2Fex_typesense/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaeyson%2Fex_typesense/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaeyson","download_url":"https://codeload.github.com/jaeyson/ex_typesense/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaeyson%2Fex_typesense/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31777350,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T00:11:49.126Z","status":"ssl_error","status_checked_at":"2026-04-14T00:10:29.837Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ecto","ecto-schemas","elixir","hacktoberfest","typesense"],"created_at":"2024-10-29T23:21:04.776Z","updated_at":"2026-04-14T01:05:33.789Z","avatar_url":"https://github.com/jaeyson.png","language":"Elixir","funding_links":[],"categories":["Elixir"],"sub_categories":[],"readme":"# ExTypesense\n\n\u003cp align=\"center\"\u003e\n  \u003csource\n    media=\"(prefers-color-scheme: dark)\"\n    srcset=\"https://raw.githubusercontent.com/typesense/typesense/refs/heads/v29/assets/typesense_logo_dark.svg\"\u003e\n  \u003cimg\n    alt=\"Typesense logo\"\n    src=\"https://raw.githubusercontent.com/typesense/typesense/refs/heads/v29/assets/typesense_logo.svg\"\n    width=\"230\"\u003e\n  \u003csource\n    media=\"(prefers-color-scheme: dark)\"\n    srcset=\"https://github.com/elixir-lang/elixir-lang.github.com/raw/main/images/logo/logo-dark.png\"\u003e\n  \u003cimg\n    alt=\"Elixir logo\"\n    src=\"https://github.com/elixir-lang/elixir-lang.github.com/raw/main/images/logo/logo.png\"\n    width=\"130\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://hex.pm/packages/ex_typesense\"\u003e\n    \u003cimg\n      alt=\"ex_typesense latest version badge\"\n      src=\"https://img.shields.io/hexpm/v/ex_typesense\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://hexdocs.pm/ex_typesense\"\u003e\n    \u003cimg\n      alt=\"ex_typesense hexdocs badge\"\n      src=\"https://img.shields.io/badge/hex-docs-lightgreen.svg\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://coveralls.io/github/jaeyson/ex_typesense?branch=main\"\u003e\n    \u003cimg\n      alt=\"Test coverage badge\"\n      src=\"https://coveralls.io/repos/github/jaeyson/ex_typesense/badge.svg?branch=main\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://app.codacy.com/gh/jaeyson/ex_typesense/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade\"\u003e\n    \u003cimg\n      alt=\"Codacy Badge\"\n      src=\"https://app.codacy.com/project/badge/Grade/24302e4486f74e498ff0a2b67e3a1f59\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://codescene.io/projects/63244\"\u003e\n    \u003cimg\n      alt=\"CodeScene Average Code Health\"\n      src=\"https://codescene.io/projects/63244/status-badges/average-code-health\"\n    \u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v29.0.yml\"\u003e\n    \u003cimg\n      alt=\"CI for v29.0\"\n      src=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v29.0.yml/badge.svg\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v28.0.yml\"\u003e\n    \u003cimg\n      alt=\"CI for v28.0\"\n      src=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v28.0.yml/badge.svg\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v27.1.yml\"\u003e\n    \u003cimg\n      alt=\"CI for v27.1\"\n      src=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v27.1.yml/badge.svg\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v27.0.yml\"\u003e\n    \u003cimg\n      alt=\"CI for v27.0\"\n      src=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v27.0.yml/badge.svg\"\n    \u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v26.0.yml\"\u003e\n    \u003cimg\n      alt=\"CI for v26.0\"\n      src=\"https://github.com/jaeyson/ex_typesense/actions/workflows/ci_v26.0.yml/badge.svg\"\n    \u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n[Typesense](https://typesense.org) client for [Elixir](https://elixir-lang.org) with support for your Ecto schemas.\n\n**Note**: the only place where ai is used/integrated is in PR reviews. I am NOT interested in adding/integrating ai generated code in my codebase, as this little library can be fit in my mental model. ai has it’s own great use case, it’s just that I wanted to be hands-on with these projects.\n\n\u003e #### OpenAPI adherence {: .tip}\n\u003e\n\u003e Under the hood, this library utilizes [open_api_typesense](https://github.com/jaeyson/open_api_typesense)\n\u003e to make sure it adheres to [Typesense's OpenAPI spec](https://github.com/typesense/typesense-api-spec).\n\n\u003e #### Upgrading to v2 {: .warning}\n\u003e\n\u003e The Purpose of v2 is to reduce code duplication (from v1) and to streamline\n\u003e passing of options (including `conn`). The breaking change here\n\u003e is that `conn` is now part of `opts` when calling functions. See example below:\n\n```elixir\n# pre-v2\nExTypesense.list_collections(conn, opts)\n\n# v2\nExTypesense.list_collections(conn: conn)\n\n# another way (v2)\nopts = [limit: 1, conn: conn]\nExTypesense.list_collections(opts)\n```\n\n## Installation\n\nExTypesense requires Elixir `~\u003e 1.14.x`. Read the [Changelog](CHANGELOG.md) for all available\nreleases and requirements. This library is published to both [Hex.pm](https://hex.pm/ex_typesense)\nand [GitHub ](https://github.com/jaeyson/ex_typesense.git) repository.\n\nAdd `:ex_typesense` to your list of dependencies in the Elixir project config file, `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    # From default Hex package manager\n    {:ex_typesense, \"~\u003e 2.1\"}\n\n    # Or from GitHub repository, if you want the latest greatest from main branch\n    {:ex_typesense, git: \"https://github.com/jaeyson/ex_typesense.git\"}\n  ]\nend\n```\n\n## Getting started\n\n### 0. (Optional) Run local Typesense instance\n\nIf you want to try this library locally:\n\n```bash\ndocker compose up -d\n```\n\nMore info on spinning a local instance: https://typesense.org/docs/guide/install-typesense.html\n\nOtherwise, go to step #1 if you're using [Cloud hosted](https://cloud.typesense.org) instance instead.\n\n### 1. Add credential to config\n\nAfter you have setup a [local](./guides/running_local_typesense.md) Typesense or\n[Cloud hosted](https://cloud.typesense.org) instance, there are 2 ways to set the credentials:\n\n\u003c!-- tabs-open --\u003e\n\n### using config\n\nOption 1: Set credentials via config (e.g. `config/runtime.exs`)\n\n```elixir\n# e.g. config/runtime.exs\nif config_env() == :prod do # if you'll use this in prod environment\n  config :open_api_typesense,\n    api_key: \"xyz\",\n    host: \"localhost\",\n    port: 8108,\n    scheme: \"http\"\n  ...\n```\n\n\u003e #### `options` key {: .tip}\n\u003e\n\u003e The `options` key can be used to pass additional configuration\n\u003e options such as custom Finch instance or receive timeout\n\u003e settings. You can add any options supported by Req here. For\n\u003e more details check [Req documentation](https://hexdocs.pm/req/Req.Steps.html#run_finch/1-request-options).\n\n\u003e #### during tests {: .tip}\n\u003e\n\u003e If you have a different config for your app, consider \n\u003e adding it in `config/test.exs`.\n\nFor Cloud hosted, you can generate and obtain the credentials from cluster instance admin interface:\n\n```elixir\nconfig :open_api_typesense,\n  api_key: \"credential\", # Admin API key\n  host: \"111222333aaabbbcc-9.x9.typesense.net\", # Nodes\n  port: 443,\n  scheme: \"https\"\n```\n\nTest if working:\n\n```elixir\nExTypesense.health()\n```\n\n### using map\n\nOption 2: Set credentials from a map\n\n\u003e #### optional `conn` {: .tip}\n\u003e\n\u003e By default you don't need to pass connections every\n\u003e time you use a function, if you use \"Option 1\" above.\n\nYou may have a `Connection` Ecto schema in your app and want to pass your own creds dynamically:\n\n```elixir\ndefmodule MyApp.Credential do\n  schema \"credentials\" do\n    field :node, :string\n    field :secret_key, :string\n    field :port, :integer\n  end\nend\n```\n\nAs long as the keys matches in [`OpenApiTypesense.Connection.t()`](https://hexdocs.pm/open_api_typesense/OpenApiTypesense.Connection.html#t:t/0):\n\n```elixir\ncredential = MyApp.Credential |\u003e where(id: ^8888) |\u003e Repo.one()\n\nconn = %{\n  host: credential.node,\n  api_key: credential.secret_key,\n  port: credential.port,\n  scheme: \"https\"\n}\n\n# NOTE: create a collection and import documents\n# first before using the command below\nExTypesense.search(collection_name, query, conn: conn)\n```\n\nOr convert your struct to map, as long as the keys matches in [`OpenApiTypesense.Connection.t()`](https://hexdocs.pm/open_api_typesense/OpenApiTypesense.Connection.html#t:t/0):\n\n```elixir\nconn = Map.from_struct(MyApp.Credential)\n\n# NOTE: create a collection and import documents\n# first before using the command below\nExTypesense.search(collection_name, query, conn: conn)\n```\n\nOr you don't want to change the fields in your Ecto schema, thus you convert it to map:\n\n```elixir\nconn = %Credential{\n  node: \"localhost\",\n  secret_key: \"xyz\",\n  port: 8108,\n  scheme: \"http\"\n}\n\nconn =\n  conn\n  |\u003e Map.from_struct()\n  |\u003e Map.drop([:node, :secret_key])\n  |\u003e Map.put(:host, conn.node)\n  |\u003e Map.put(:api_key, conn.secret_key)\n\n# NOTE: create a collection and import documents\n# first before using the command below\nExTypesense.search(collection_name, query, conn: conn)\n```\n\nOr just plain map\n\n```elixir\nconn = %{\n    host: \"127.0.0.1\",\n    api_key: \"xyz\",\n    port: 8108,\n    scheme: \"http\"\n}\n\nExTypesense.health(conn: conn)\n```\n\n\u003c!-- tabs-close --\u003e\n\n### 2. Create a collection\n\nThere are 2 ways to create a collection, either via\n[Ecto schema](https://hexdocs.pm/ecto/Ecto.Schema.html) or using map\n([an Elixir data type](https://hexdocs.pm/elixir/keywords-and-maps.html#maps-as-key-value-pairs)):\n\n\u003c!-- tabs-open --\u003e\n\n### using Ecto schema\n\n\u003e #### added fk in schema {: .info}\n\u003e\n\u003e The format we're using is `\u003cTABLE_NAME\u003e_id`. If you have table e.g. named `persons`,\n\u003e it'll be `persons_id`.\n\u003e\n\u003e `persons_id` is of type `integer`: read the discussion on why\n\u003e we need to add [default_sorting_field](https://github.com/typesense/typesense/issues/72#issuecomment-645725013).\n\n```elixir\ndefmodule Person do\n  use Ecto.Schema\n  @behaviour ExTypesense\n\n  # In this example, we're adding `persons_id`\n  # that points to the id of `persons` schema.\n\n  defimpl Jason.Encoder, for: __MODULE__ do\n    def encode(value, opts) do\n      value\n      |\u003e Map.take([:id, :persons_id, :name, :country])\n      |\u003e Enum.map(fn {key, val} -\u003e\n        cond do\n          key === :id -\u003e {key, to_string(Map.get(value, :id))}\n          key === :persons_id -\u003e {key, Map.get(value, :id)}\n          true -\u003e {key, val}\n        end\n      end)\n      |\u003e Enum.into(%{})\n      |\u003e Jason.Encode.map(opts)\n    end\n  end\n\n  schema \"persons\" do\n    field(:name, :string)\n    field(:country, :string)\n    field(:persons_id, :integer, virtual: true)\n  end\n\n  @impl ExTypesense\n  def get_field_types do\n      name = __MODULE__.__schema__(:source)\n      primary_field = name \u003c\u003e \"_id\"\n\n    %{\n      name: name,\n      default_sorting_field: primary_field,\n      fields: [\n        %{name: primary_field, type: \"int32\"},\n        %{name: \"name\", type: \"string\"},\n        %{name: \"country\", type: \"string\"}\n      ]\n    }\n  end\nend\n```\n\nNext, create the collection from a module name.\n\n```elixir\nExTypesense.create_collection(Person)\n```\n\n### using map\n\n```elixir\nschema = %{\n  name: \"companies\",\n  fields: [\n    %{name: \"company_name\", type: \"string\"},\n    %{name: \"companies_id\", type: \"int32\"},\n    %{name: \"country\", type: \"string\"}\n  ],\n  default_sorting_field: \"companies_id\"\n}\n\nExTypesense.create_collection(schema)\n```\n\n\u003c!-- tabs-close --\u003e\n\n### 3. Indexing documents\n\n\u003c!-- tabs-open --\u003e\n\n### single (Ecto)\n\n```elixir\nPost |\u003e Repo.get!(123) |\u003e ExTypesense.index_document()\n```\n\n### multiple (Ecto)\n\n```elixir\nPost |\u003e Repo.all() |\u003e ExTypesense.import_documents()\n```\n\n### single (map)\n\n```elixir\ndocument = %{\n  collection_name: \"companies\",\n  company_name: \"Test\",\n  doc_companies_id: 103,\n  country: \"AL\"\n}\n\nExTypesense.index_document(document)\n\n# or explicitly pass the collection name\ndocument = %{\n  company_name: \"Test\",\n  doc_companies_id: 103,\n  country: \"AL\"\n}\n\nExTypesense.index_document(\"companies\", document)\n```\n\n### multiple (list of maps)\n\n```elixir\nmultiple_documents = [\n  %{\n    company_name: \"Boca Cola\",\n    doc_companies_id: 827,\n    country: \"SG\"\n  },\n  %{\n    company_name: \"Motor, Inc.\",\n    doc_companies_id: 549,\n    country: \"TW\"\n  }\n]\n\nExTypesense.import_documents(\"companies\", multiple_documents)\n```\n\n\u003c!-- tabs-close --\u003e\n\n### 4. Search\n\n```elixir\nparams = %{q: \"John Doe\", query_by: \"name\"}\n\n# using string collection name\nExTypesense.search(schema.name, params)\n\n# or module name\nExTypesense.search(Person, params)\n```\n\nCheck [Cheatsheet](https://hexdocs.pm/ex_typesense/cheatsheet.html) for more usage examples.\n\n## Miscellaneous\n\n## Adding [cache, retry, compress_body](https://hexdocs.pm/req/Req.html#new/1) in the built in client\n\nE.g. when a user wants to change `retry` and `cache` options\n\n```elixir\nExTypesense.get_collection(\"companies\", req: [retry: false, cache: true])\n```\n\nSee implementation: https://github.com/jaeyson/open_api_typesense/blob/main/lib/open_api_typesense/client.ex#L82\n\n### Use non-default Finch adapter\n\nFor instance, in a scenario where an application has multiple Finch pools configured for\ndifferent services, a developer might want to specify a particular Finch pool for the\n`HttpClient` to use. This can be achieved by configuring the options as follows:\n\n```elixir\nconfig :open_api_typesense,\n  api_key: \"XXXXXX\",\n  #...\n  options: [finch: MyApp.CustomFinch] # \u003c- add options\n```\n\nIn this example, `MyApp.CustomFinch` is a custom Finch pool that the developer has\nconfigured with specific connection options or other settings that differ from the\ndefault Finch pool.\n\n## Using another client\n\nBy default this library is using [Req](https://hexdocs.pm/req/readme.html). In order to use another HTTP client,\nOpenApiTypesense has a callback function ([Behaviours](https://hexdocs.pm/elixir/typespecs.html#behaviours))\ncalled `request` that contains 2 args:\n\n1. `conn`: your connection map\n2. `params`: payload, header, and client-related stuffs.\n\n\u003e #### `conn` and `params` {: .info}\n\u003e\n\u003e you can change the name `conn` and/or `params` however you want,\n\u003e since it's just a variable.\n\nHere's a custom client example ([HTTPoison](https://hexdocs.pm/httpoison/readme.html)) in order to match the usage:\n\n\u003c!-- tabs-open --\u003e\n\n### Client module\n\n```elixir\ndefmodule MyApp.CustomClient do\n  @behaviour OpenApiTypesense.Client\n  \n  @impl OpenApiTypesense.Client\n  def request(conn, params) do\n    url = %URI{\n      scheme: conn.scheme,\n      host: conn.host,\n      port: conn.port,\n      path: params.url,\n      query: URI.encode_query(params[:query] || %{})\n    }\n    |\u003e URI.to_string()\n\n    request = %HTTPoison.Request{method: params.method, url: url}\n\n    request =\n      if params[:request] do\n        [{content_type, _schema}] = params.request\n\n        headers = [\n          {\"X-TYPESENSE-API-KEY\", conn.api_key}\n          {\"Content-Type\", content_type}\n        ]\n\n        %{request | headers: headers}\n      else\n        request\n      end\n\n    request =\n      if params[:body] do\n        %{request | body: Jason.encode!(params.body)}\n      else\n        request\n      end\n\n    HTTPoison.request!(request)\n  end\nend\n```\n\n### Client config\n\n```elixir\nconfig :open_api_typesense,\n  api_key: \"xyz\", # Admin API key\n  host: \"localhost\", # Nodes\n  port: 8108,\n  scheme: \"http\",\n  client: MyApp.CustomClient # \u003c- add this\n```\n\n\u003c!-- tabs-close --\u003e\n\nVisit `open_api_typesense` docs for further [examples](https://hexdocs.pm/open_api_typesense/custom_http_client.html)\n\n## License\n\nCopyright (c) 2021 Jaeyson Anthony Y.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaeyson%2Fex_typesense","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaeyson%2Fex_typesense","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaeyson%2Fex_typesense/lists"}