Using the Brave Search API
You need to sign up for a free or paid account on the Brave search page and set an environment variable to your assigned API key:
export BRAVE_SEARCH_API_KEY = BSAgQ-Nc5.....
The Brave Search API allows you to access Brave Search results directly within your applications or services. It provides developers with the ability to harness the privacy-focused and independent search capabilities of Brave, returning results for web searches, news, videos, and more. To obtain an API key, simply create an account on the Brave Search API website and subscribe to either the free or one of the paid plans. The Brave Search API offers flexible pricing tiers, including a free option for testing and development, making it accessible to a wide range of users and projects. Currently you can call the API 2000 times a month on the free tier.
The library developed in this chapter is implemented in a single file BraveSearch.hs:
1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE RecordWildCards #-}
3 -- Module: BraveSearch
4 -- Minimal client for the Brave Search API; exposes `getSearchSuggestions`.
5 -- Uses OverloadedStrings for convenient Text literals and RecordWildCards for concise pattern binding.
6
7 module BraveSearch
8 ( getSearchSuggestions
9 ) where
10
11 import Network.HTTP.Simple -- HTTP request/response helpers (parseRequest, setRequestHeader, httpLBS)
12 import Data.Text.Encoding (encodeUtf8) -- convert Text -> ByteString for query params
13 import Data.Aeson -- FromJSON and decoding (eitherDecode, (.:), (.:?))
14 import qualified Data.Text as T -- strict Text type
15 import Control.Exception (try) -- catch exceptions and return Either
16 import Network.HTTP.Client (HttpException) -- HTTP error type
17 import qualified Data.ByteString.Char8 as BS -- UTF-8 ByteString for headers
18 -- unused: Data.ByteString.Lazy.Char8
19
20 -- Top-level response from the Brave Search API
21 data SearchResponse = SearchResponse
22 { query :: QueryInfo
23 , web :: WebResults
24 } deriving (Show)
25
26 -- Info about the original query the API received
27 data QueryInfo = QueryInfo
28 { original :: T.Text
29 } deriving (Show)
30
31 -- Container for the list of web results
32 data WebResults = WebResults
33 { results :: [WebResult]
34 } deriving (Show)
35
36 -- One result item; several fields are optional (`Maybe`)
37 data WebResult = WebResult
38 { type_ :: T.Text
39 , index :: Maybe Int
40 , all :: Maybe Bool
41 , title :: Maybe T.Text
42 , url :: Maybe T.Text
43 , description :: Maybe T.Text
44 } deriving (Show)
45
46 -- JSON decoders mapping API fields to our Haskell types
47 instance FromJSON SearchResponse where
48 parseJSON = withObject "SearchResponse" $ \v -> SearchResponse
49 <$> v .: "query"
50 <*> v .: "web"
51
52 instance FromJSON QueryInfo where
53 parseJSON = withObject "QueryInfo" $ \v -> QueryInfo
54 <$> v .: "original"
55
56 instance FromJSON WebResults where
57 parseJSON = withObject "WebResults" $ \v -> WebResults
58 <$> v .: "results"
59
60 -- Use (.:) for required fields and (.:?) for optional ones
61 instance FromJSON WebResult where
62 parseJSON = withObject "WebResult" $ \v -> WebResult
63 <$> v .: "type"
64 <*> v .:? "index"
65 <*> v .:? "all"
66 <*> v .:? "title"
67 <*> v .:? "url"
68 <*> v .:? "description"
69
70 -- | Perform a Brave Search with the given API key (as raw bytes) and text query.
71 getSearchSuggestions :: BS.ByteString -> T.Text -> IO (Either T.Text [T.Text])
72 getSearchSuggestions apiKey query = do
73 -- Build base request
74 let baseUrl = "https://api.search.brave.com/res/v1/web/search"
75 request0 <- parseRequest baseUrl
76 -- Add query parameters (URL-encoded) and headers
77 let request1 = setRequestQueryString
78 [ ("q", Just $ encodeUtf8 query)
79 , ("country", Just "US")
80 , ("count", Just "5")
81 ]
82 request0
83 request = setRequestHeader "Accept" ["application/json"]
84 $ setRequestHeader "X-Subscription-Token" [apiKey]
85 $ request1
86
87 -- Run the request and catch exceptions as Either
88 result <- try $ httpLBS request
89
90 -- Unwrap the result and handle errors (network, non-200 status, JSON)
91 case result of
92 Left e -> return . Left $ T.pack $ "Network error: " ++ show (e :: HttpException)
93 Right response ->
94 let status = getResponseStatusCode response
95 in if status /= 200
96 then return . Left $ T.pack $ "HTTP error: " ++ show status
97 else case eitherDecode (getResponseBody response) of
98 Left err -> return . Left $ T.pack $ "JSON parsing error: " ++ err
99 Right SearchResponse{..} ->
100 let originalQuery = original query
101 webResults = results web
102 suggestions = ("Original Query: " <> originalQuery)
103 : map formatResult webResults
104 in return $ Right suggestions
105
106 -- Format a single WebResult into a readable line
107 formatResult :: WebResult -> T.Text
108 formatResult WebResult{..} =
109 let titleText = maybe "N/A" ("Title: " <>) title
110 urlText = maybe "N/A" ("URL: " <>) url
111 descText = maybe "N/A" ("Description: " <>) (fmap (T.take 100) description) -- truncate description
112 in T.intercalate " | " [titleText, urlText, descText]
Haskell Code Description for Brave Search Suggestions
This Haskell code implements a function, getSearchSuggestions, that fetches search suggestions from the Brave Search API.
Functionality:
-
getSearchSuggestions:- Takes an API key and a search query as input.
- Constructs a URL to send a request to the Brave Search API, specifying the query, country, and result count.
- Sets up the HTTP request with necessary headers, including the API key.
- Makes the request and handles potential network errors.
- Checks the response status code. If it’s 200 (OK), proceeds to parse the JSON response.
- Extracts search results and formats them, including the original query.
- Returns either an error message (if something went wrong) or a list of formatted search suggestions.
Key Features:
-
Data Types:
- Defines data types to model the JSON structure of the Brave Search API response, including
SearchResponse,QueryInfo,WebResults, andWebResult.
-
JSON Parsing:
- Uses the
aesonlibrary to parse the JSON response into the defined data types.
-
Error Handling:
- Employs
tryfrom theControl.Exceptionmodule to gracefully handle potential network errors during the HTTP request.
-
HTTP Request:
- Utilizes the
Network.HTTP.Simplelibrary to make the HTTP request to the Brave Search API.
-
Formatting:
- The
formatResultfunction formats each search result into a user-friendly string, including the title, URL, and a shortened description.
Libraries Used:
Network.HTTP.Simple- For making HTTP requests.Data.Aeson- For JSON parsing and encoding.Data.Text- For efficient text handling.Control.Exception- For error handling.Network.HTTP.Client- For additional HTTP functionalities.Data.ByteString.Char8andData.ByteString.Lazy.Char8- For working with byte strings.
Language Extensions:
OverloadedStrings- Allows the use of string literals asTextvalues.RecordWildCards- Enables convenient access to record fields using wildcards.
Overall:
This code provides a basic but functional way to interact with the Brave Search API to retrieve and format search suggestions. It demonstrates good practices in Haskell programming, including data modeling, error handling, and the use of relevant libraries.
Here is an example Main.hs file to use this library:
1 {-# LANGUAGE OverloadedStrings #-}
2 -- Allows string literals like "foo" to be used as `Text`
3 module Main where
4
5 import BraveSearch (getSearchSuggestions)
6 import qualified Data.ByteString.Char8 as BS
7 import System.Environment (getEnv)
8 import qualified Data.Text as T
9 import qualified Data.Text.IO as TIO
10
11 -- Entry point: runs an interactive search
12 main :: IO ()
13 main = do
14 -- Get the API key from the environment variable
15 -- Read API key from environment and convert to ByteString
16 apiKeyRaw <- getEnv "BRAVE_SEARCH_API_KEY"
17 let apiKey = BS.pack apiKeyRaw
18
19 -- Prompt the user for a search query
20 TIO.putStrLn "Enter a search query:"
21 query <- TIO.getLine
22
23 -- Call the function to get search suggestions
24 result <- getSearchSuggestions apiKey query
25
26 -- Handle `Either`: Left is an error, Right is a list of suggestion lines
27 case result of
28 Left err -> TIO.putStrLn $ "Error: " <> err
29 Right suggestions -> do
30 TIO.putStrLn "Search suggestions:"
31 mapM_ (TIO.putStrLn . ("- " <>)) suggestions -- print each suggestion
Test Code Explanation
The code interacts with the BraveSearch module to demonstrate how to fetch and display search suggestions from the Brave Search API.
Code Breakdown
-
Imports
-
It imports:
BraveSearchto use thegetSearchSuggestionsfunction.System.Environmentto get the API key from an environment variable.Data.TextandData.Text.IOfor working with text input and output.
-
-
mainFunction-
Get API Key
- It uses
getEnv "BRAVE_SEARCH_API_KEY"to retrieve the Brave Search API key from an environment variable namedBRAVE_SEARCH_API_KEY. This assumes you have set this environment variable in your system before running the code.
-
Prompt for Query
- It prints the message “Enter a search query:” to the console, prompting the user to input a search term.
- It reads the user’s input using
TIO.getLineand stores it in thequeryvariable.
-
Fetch Search Suggestions
- It calls the
getSearchSuggestionsfunction from theBraveSearchmodule, passing the API key and the user’s query (converted fromTexttoStringusingT.unpack). - It stores the result of this call in the
resultvariable.
-
Handle Result
-
It uses a
caseexpression to handle the two possible outcomes of thegetSearchSuggestionscall:-
Left err:- If there was an error (e.g., network issue, HTTP error, JSON parsing error), it prints the error message prefixed with “Error:”.
-
Right suggestions:-
If the call was successful and returned a list of search suggestions:
- Prints “Search suggestions:” to the console.
- Uses
mapM_to iterate over thesuggestionslist and print each suggestion in the following format:- suggestion text
-
-
-
-
This test code provides a basic example of how to use the getSearchSuggestions function from the BraveSearch module.
Here is the output:
$ cabal run
Enter a search query:
find a consultant for AI and common lisp, and the semantic web
Search suggestions:
- Original Query: find a consultant for AI and common lisp, and the semantic web
- Title: Mark Watson: AI Practitioner and Lisp Hacker | URL: https://markwatson.com/ | Description: I am the author of 20+ books on Artificial Intelligence, <strong>Common</strong> <strong>Lisp</stron
- Title: Lisp (programming language) - Wikipedia | URL: https://en.wikipedia.org/wiki/Lisp_(programming_language) | Description: Scheme is a statically scoped and properly tail-recursive dialect of <strong>the</strong> <strong>Li
- Title: The Lisp approach to AI (Part 1). If you are a programmer that reads… | by Sebastian Valencia | AI Society | Medium | URL: https://medium.com/ai-society/the-lisp-approach-to-ai-part-1-a48c7385a913 | Description: If you are a programmer that reads about the history and random facts of this lovely craft